From 3ba3df9043c2f40c7b8cf73dc43210b58222c859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20F=C3=B8lstad?= Date: Mon, 20 Jul 2020 19:47:44 +0200 Subject: [PATCH] Release/0.1.8 (#8) * Add sql support * Add better styling * Bug fixes * Refactoring --- LICENSE | 2 +- README.md | 2 +- pom.xml | 19 +- .../com/erikmafo/btviewer/FXMLLoaderUtil.java | 21 ++ .../java/com/erikmafo/btviewer/MainApp.java | 6 +- ...anceDialog.java => AddInstanceDialog.java} | 35 ++- .../components/BigtableTableView.java | 164 ------------ .../components/BigtableTablesListView.java | 128 --------- .../components/BigtableViewController.java | 166 ++++++++++++ .../components/CredentialsPathDialog.java | 96 +++++++ .../components/QueryBoxController.java | 128 +++++++++ .../btviewer/components/RowSelectionView.java | 49 ---- .../SpecifyCredentialsPathDialog.java | 106 -------- .../components/SyntaxHighlightingUtil.java | 55 ++++ ...esDialog.java => TableSettingsDialog.java} | 176 ++++++------- .../erikmafo/btviewer/config/AppConfig.java | 17 +- .../btviewer/config/ConfigInjectionUtil.java | 1 + .../btviewer/controllers/MainController.java | 175 +------------ .../controllers/MenuBarController.java | 4 +- .../BigtableProjectTreeItemExpanded.java | 23 -- .../btviewer/events/ScanTableAction.java | 25 -- .../InvalidCredentialsRecordException.java | 22 -- .../erikmafo/btviewer/model/BigtableCell.java | 6 +- .../btviewer/model/BigtableInstance.java | 8 +- .../btviewer/model/BigtableReadRequest.java | 29 ++- .../model/BigtableReadRequestBuilder.java | 22 +- .../erikmafo/btviewer/model/BigtableRow.java | 11 +- .../btviewer/model/BigtableRowRange.java | 12 +- .../btviewer/model/BigtableTable.java | 6 + ...ration.java => BigtableTableSettings.java} | 10 +- .../model/BigtableValueConverter.java | 25 +- .../model/ByteStringConverterImpl.java | 51 ++++ .../projectexplorer/InstanceTreeItem.java | 65 +++++ .../ProjectExplorerController.java | 123 +++++++++ .../ProjectItemMenuController.java | 51 ++++ .../projectexplorer/ProjectTreeItem.java | 59 +++++ .../projectexplorer/RootTreeItem.java | 65 +++++ .../TableItemMenuController.java | 18 ++ .../projectexplorer/TableTreeItem.java | 11 + .../projectexplorer/TreeItemData.java | 103 ++++++++ .../btviewer/services/ListTablesService.java | 57 ++-- .../services/LoadInstancesService.java | 19 +- .../services/LoadProjectsService.java | 28 ++ .../LoadTableConfigurationService.java | 34 --- .../services/LoadTableSettingsService.java | 43 ++++ .../btviewer/services/ReadRowsService.java | 60 +++-- .../services/RemoveProjectService.java | 35 +++ .../services/SaveInstanceService.java | 37 +++ .../services/SaveInstancesService.java | 44 ---- ...ice.java => SaveTableSettingsService.java} | 18 +- .../btviewer/services/ServicesModule.java | 46 ++-- .../services/internal/AppDataStorage.java | 26 ++ .../services/internal/AppDataStorageImpl.java | 164 ++++++++++++ .../BigtableEmulatorSettingsProvider.java | 2 +- .../internal/BigtableInstanceManager.java | 12 - .../internal/BigtableInstanceManagerImpl.java | 70 ----- .../services/internal/TableConfigManager.java | 13 - .../internal/TableConfigManagerImpl.java | 55 ---- .../services/internal/TestDataUtil.java | 83 ++++++ .../inmemory/InMemoryInstanceManager.java | 28 -- .../inmemory/InMemoryTableConfigManager.java | 22 -- .../internal/inmemory/TestDataUtil.java | 90 ------- .../btviewer/sql/ByteStringConverter.java | 7 + .../btviewer/sql/DateTimeFormatUtil.java | 65 +++++ .../java/com/erikmafo/btviewer/sql/Field.java | 50 ++++ .../com/erikmafo/btviewer/sql/Operator.java | 26 ++ .../erikmafo/btviewer/sql/QueryConverter.java | 243 ++++++++++++++++++ .../com/erikmafo/btviewer/sql/QueryType.java | 5 + .../erikmafo/btviewer/sql/ReservedWord.java | 65 +++++ .../com/erikmafo/btviewer/sql/SqlParser.java | 140 ++++++++++ .../erikmafo/btviewer/sql/SqlParserStep.java | 15 ++ .../com/erikmafo/btviewer/sql/SqlQuery.java | 58 +++++ .../com/erikmafo/btviewer/sql/SqlToken.java | 57 ++++ .../erikmafo/btviewer/sql/SqlTokenType.java | 17 ++ .../erikmafo/btviewer/sql/SqlTokenizer.java | 78 ++++++ .../java/com/erikmafo/btviewer/sql/Value.java | 49 ++++ .../com/erikmafo/btviewer/sql/ValueType.java | 6 + .../erikmafo/btviewer/sql/WhereClause.java | 39 +++ .../com/erikmafo/btviewer/util/AlertUtil.java | 18 ++ .../util/ByteStringConverterUtil.java | 45 ++++ src/main/resources/config.properties | 3 +- src/main/resources/css/main.css | 6 + src/main/resources/css/query_box.css | 17 ++ ...e_dialog.fxml => add_instance_dialog.fxml} | 0 .../resources/fxml/bigtable_table_view.fxml | 14 - .../fxml/bigtable_tables_list_view.fxml | 8 - src/main/resources/fxml/bigtable_view.fxml | 17 ++ ...alog.fxml => credentials_path_dialog.fxml} | 2 +- src/main/resources/fxml/main.fxml | 15 +- .../fxml/manage_credentials_dialog_pane.fxml | 2 +- src/main/resources/fxml/project_explorer.fxml | 11 + .../resources/fxml/project_item_menu.fxml | 13 + src/main/resources/fxml/query_box.fxml | 23 ++ .../resources/fxml/row_selection_view.fxml | 19 -- src/main/resources/fxml/table_item_menu.fxml | 12 + ...dialog.fxml => table_settings_dialog.fxml} | 8 +- .../com/erikmafo/btviewer/sql/FieldTest.java | 15 ++ .../erikmafo/btviewer/sql/SqlParserTest.java | 31 +++ .../btviewer/sql/SqlTokenizerTest.java | 70 +++++ 99 files changed, 2972 insertions(+), 1378 deletions(-) create mode 100644 src/main/java/com/erikmafo/btviewer/FXMLLoaderUtil.java rename src/main/java/com/erikmafo/btviewer/components/{BigtableInstanceDialog.java => AddInstanceDialog.java} (66%) delete mode 100644 src/main/java/com/erikmafo/btviewer/components/BigtableTableView.java delete mode 100644 src/main/java/com/erikmafo/btviewer/components/BigtableTablesListView.java create mode 100644 src/main/java/com/erikmafo/btviewer/components/BigtableViewController.java create mode 100644 src/main/java/com/erikmafo/btviewer/components/CredentialsPathDialog.java create mode 100644 src/main/java/com/erikmafo/btviewer/components/QueryBoxController.java delete mode 100644 src/main/java/com/erikmafo/btviewer/components/RowSelectionView.java delete mode 100644 src/main/java/com/erikmafo/btviewer/components/SpecifyCredentialsPathDialog.java create mode 100644 src/main/java/com/erikmafo/btviewer/components/SyntaxHighlightingUtil.java rename src/main/java/com/erikmafo/btviewer/components/{BigtableValueTypesDialog.java => TableSettingsDialog.java} (66%) delete mode 100644 src/main/java/com/erikmafo/btviewer/events/BigtableProjectTreeItemExpanded.java delete mode 100644 src/main/java/com/erikmafo/btviewer/events/ScanTableAction.java delete mode 100644 src/main/java/com/erikmafo/btviewer/exceptions/InvalidCredentialsRecordException.java rename src/main/java/com/erikmafo/btviewer/model/{BigtableTableConfiguration.java => BigtableTableSettings.java} (72%) create mode 100644 src/main/java/com/erikmafo/btviewer/model/ByteStringConverterImpl.java create mode 100644 src/main/java/com/erikmafo/btviewer/projectexplorer/InstanceTreeItem.java create mode 100644 src/main/java/com/erikmafo/btviewer/projectexplorer/ProjectExplorerController.java create mode 100644 src/main/java/com/erikmafo/btviewer/projectexplorer/ProjectItemMenuController.java create mode 100644 src/main/java/com/erikmafo/btviewer/projectexplorer/ProjectTreeItem.java create mode 100644 src/main/java/com/erikmafo/btviewer/projectexplorer/RootTreeItem.java create mode 100644 src/main/java/com/erikmafo/btviewer/projectexplorer/TableItemMenuController.java create mode 100644 src/main/java/com/erikmafo/btviewer/projectexplorer/TableTreeItem.java create mode 100644 src/main/java/com/erikmafo/btviewer/projectexplorer/TreeItemData.java create mode 100644 src/main/java/com/erikmafo/btviewer/services/LoadProjectsService.java delete mode 100644 src/main/java/com/erikmafo/btviewer/services/LoadTableConfigurationService.java create mode 100644 src/main/java/com/erikmafo/btviewer/services/LoadTableSettingsService.java create mode 100644 src/main/java/com/erikmafo/btviewer/services/RemoveProjectService.java create mode 100644 src/main/java/com/erikmafo/btviewer/services/SaveInstanceService.java delete mode 100644 src/main/java/com/erikmafo/btviewer/services/SaveInstancesService.java rename src/main/java/com/erikmafo/btviewer/services/{SaveTableConfigurationService.java => SaveTableSettingsService.java} (51%) create mode 100644 src/main/java/com/erikmafo/btviewer/services/internal/AppDataStorage.java create mode 100644 src/main/java/com/erikmafo/btviewer/services/internal/AppDataStorageImpl.java rename src/main/java/com/erikmafo/btviewer/services/internal/{inmemory => }/BigtableEmulatorSettingsProvider.java (96%) delete mode 100644 src/main/java/com/erikmafo/btviewer/services/internal/BigtableInstanceManager.java delete mode 100644 src/main/java/com/erikmafo/btviewer/services/internal/BigtableInstanceManagerImpl.java delete mode 100644 src/main/java/com/erikmafo/btviewer/services/internal/TableConfigManager.java delete mode 100644 src/main/java/com/erikmafo/btviewer/services/internal/TableConfigManagerImpl.java create mode 100644 src/main/java/com/erikmafo/btviewer/services/internal/TestDataUtil.java delete mode 100644 src/main/java/com/erikmafo/btviewer/services/internal/inmemory/InMemoryInstanceManager.java delete mode 100644 src/main/java/com/erikmafo/btviewer/services/internal/inmemory/InMemoryTableConfigManager.java delete mode 100644 src/main/java/com/erikmafo/btviewer/services/internal/inmemory/TestDataUtil.java create mode 100644 src/main/java/com/erikmafo/btviewer/sql/ByteStringConverter.java create mode 100644 src/main/java/com/erikmafo/btviewer/sql/DateTimeFormatUtil.java create mode 100644 src/main/java/com/erikmafo/btviewer/sql/Field.java create mode 100644 src/main/java/com/erikmafo/btviewer/sql/Operator.java create mode 100644 src/main/java/com/erikmafo/btviewer/sql/QueryConverter.java create mode 100644 src/main/java/com/erikmafo/btviewer/sql/QueryType.java create mode 100644 src/main/java/com/erikmafo/btviewer/sql/ReservedWord.java create mode 100644 src/main/java/com/erikmafo/btviewer/sql/SqlParser.java create mode 100644 src/main/java/com/erikmafo/btviewer/sql/SqlParserStep.java create mode 100644 src/main/java/com/erikmafo/btviewer/sql/SqlQuery.java create mode 100644 src/main/java/com/erikmafo/btviewer/sql/SqlToken.java create mode 100644 src/main/java/com/erikmafo/btviewer/sql/SqlTokenType.java create mode 100644 src/main/java/com/erikmafo/btviewer/sql/SqlTokenizer.java create mode 100644 src/main/java/com/erikmafo/btviewer/sql/Value.java create mode 100644 src/main/java/com/erikmafo/btviewer/sql/ValueType.java create mode 100644 src/main/java/com/erikmafo/btviewer/sql/WhereClause.java create mode 100644 src/main/java/com/erikmafo/btviewer/util/AlertUtil.java create mode 100644 src/main/java/com/erikmafo/btviewer/util/ByteStringConverterUtil.java create mode 100644 src/main/resources/css/query_box.css rename src/main/resources/fxml/{bigtable_instance_dialog.fxml => add_instance_dialog.fxml} (100%) delete mode 100644 src/main/resources/fxml/bigtable_table_view.fxml delete mode 100644 src/main/resources/fxml/bigtable_tables_list_view.fxml create mode 100644 src/main/resources/fxml/bigtable_view.fxml rename src/main/resources/fxml/{specify_credentials_path_dialog.fxml => credentials_path_dialog.fxml} (83%) create mode 100644 src/main/resources/fxml/project_explorer.fxml create mode 100644 src/main/resources/fxml/project_item_menu.fxml create mode 100644 src/main/resources/fxml/query_box.fxml delete mode 100644 src/main/resources/fxml/row_selection_view.fxml create mode 100644 src/main/resources/fxml/table_item_menu.fxml rename src/main/resources/fxml/{bigtable_value_types_dialog.fxml => table_settings_dialog.fxml} (95%) create mode 100644 src/test/java/com/erikmafo/btviewer/sql/FieldTest.java create mode 100644 src/test/java/com/erikmafo/btviewer/sql/SqlParserTest.java create mode 100644 src/test/java/com/erikmafo/btviewer/sql/SqlTokenizerTest.java diff --git a/LICENSE b/LICENSE index 94a7ad5..1a94d84 100644 --- a/LICENSE +++ b/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 6e86686..371cd2f 100644 --- a/README.md +++ b/README.md @@ -38,4 +38,4 @@ Click OK and the application updates the view of the table ## Licence -See [licence file](LICENSE). \ No newline at end of file +See [licence file](LICENSE). diff --git a/pom.xml b/pom.xml index daa64c5..d6f9712 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.erikmafo bigtableviewer - 0.1.7 + 0.1.8 jar bigtableviewer @@ -173,6 +173,21 @@ + + org.mapdb + mapdb + 3.0.8 + + + org.kordamp.bootstrapfx + bootstrapfx-core + 0.2.4 + + + org.fxmisc.richtext + richtextfx + 0.10.5 + com.google.cloud google-cloud-bigtable @@ -244,7 +259,7 @@ com.google.inject guice - 4.2.2 + 4.2.3 \ No newline at end of file diff --git a/src/main/java/com/erikmafo/btviewer/FXMLLoaderUtil.java b/src/main/java/com/erikmafo/btviewer/FXMLLoaderUtil.java new file mode 100644 index 0000000..e71e9a2 --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/FXMLLoaderUtil.java @@ -0,0 +1,21 @@ +package com.erikmafo.btviewer; + +import com.erikmafo.btviewer.services.ServicesModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import javafx.fxml.FXMLLoader; + +import java.io.IOException; + +public class FXMLLoaderUtil { + public static void loadFxml(String fxmlFile, Object controller) { + var loader = new FXMLLoader(controller.getClass().getResource(fxmlFile)); + loader.setRoot(controller); + loader.setController(controller); + try { + loader.load(); + } catch (IOException e) { + throw new RuntimeException("Could not load fxml file: " + fxmlFile, e); + } + } +} diff --git a/src/main/java/com/erikmafo/btviewer/MainApp.java b/src/main/java/com/erikmafo/btviewer/MainApp.java index acd1cd3..27bed61 100644 --- a/src/main/java/com/erikmafo/btviewer/MainApp.java +++ b/src/main/java/com/erikmafo/btviewer/MainApp.java @@ -19,10 +19,12 @@ public static void main(String[] args) { @Override public void start(Stage primaryStage) throws Exception{ FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/main.fxml")); - loader.setControllerFactory(Guice.createInjector(new ServicesModule())::getInstance); + var injector = Guice.createInjector(new ServicesModule()); + loader.setControllerFactory(injector::getInstance); Parent root = loader.load(); - primaryStage.setTitle("Bigtable viewer"); + primaryStage.setTitle("Bigtable Viewer"); primaryStage.setScene(new Scene(root, 800, 700)); + primaryStage.getScene().getStylesheets().add("org/kordamp/bootstrapfx/bootstrapfx.css"); primaryStage.show(); } diff --git a/src/main/java/com/erikmafo/btviewer/components/BigtableInstanceDialog.java b/src/main/java/com/erikmafo/btviewer/components/AddInstanceDialog.java similarity index 66% rename from src/main/java/com/erikmafo/btviewer/components/BigtableInstanceDialog.java rename to src/main/java/com/erikmafo/btviewer/components/AddInstanceDialog.java index e5f5c08..0882762 100644 --- a/src/main/java/com/erikmafo/btviewer/components/BigtableInstanceDialog.java +++ b/src/main/java/com/erikmafo/btviewer/components/AddInstanceDialog.java @@ -1,4 +1,6 @@ package com.erikmafo.btviewer.components; + +import com.erikmafo.btviewer.FXMLLoaderUtil; import com.erikmafo.btviewer.model.BigtableInstance; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; @@ -7,10 +9,9 @@ import javafx.scene.control.DialogPane; import javafx.scene.control.TextField; -import java.io.IOException; import java.util.concurrent.CompletableFuture; -public class BigtableInstanceDialog extends DialogPane { +public class AddInstanceDialog extends DialogPane { @FXML private TextField projectIdTextField; @@ -18,17 +19,8 @@ public class BigtableInstanceDialog extends DialogPane { @FXML private TextField instanceIdTextField; - private BigtableInstanceDialog() { - - FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/bigtable_instance_dialog.fxml")); - loader.setRoot(this); - loader.setController(this); - - try { - loader.load(); - } catch (IOException e) { - throw new RuntimeException(e); - } + private AddInstanceDialog() { + FXMLLoaderUtil.loadFxml("/fxml/add_instance_dialog.fxml", this); } private BigtableInstance getBigtableInstance() { @@ -37,12 +29,27 @@ private BigtableInstance getBigtableInstance() { instanceIdTextField.getText()); } + public void preFillProjectId(String projectId) { + projectIdTextField.setText(projectId); + projectIdTextField.setEditable(false); + instanceIdTextField.requestFocus(); + } + public static CompletableFuture displayAndAwaitResult() { + return displayAndAwaitResult(null); + } + + public static CompletableFuture displayAndAwaitResult(String projectId) { CompletableFuture future = new CompletableFuture<>(); try { Dialog dialog = new Dialog<>(); - BigtableInstanceDialog pane = new BigtableInstanceDialog(); + AddInstanceDialog pane = new AddInstanceDialog(); + + if (projectId != null) { + pane.preFillProjectId(projectId); + } + dialog.setDialogPane(pane); dialog.setResultConverter(buttonType -> { diff --git a/src/main/java/com/erikmafo/btviewer/components/BigtableTableView.java b/src/main/java/com/erikmafo/btviewer/components/BigtableTableView.java deleted file mode 100644 index be876e9..0000000 --- a/src/main/java/com/erikmafo/btviewer/components/BigtableTableView.java +++ /dev/null @@ -1,164 +0,0 @@ -package com.erikmafo.btviewer.components; - -import com.erikmafo.btviewer.model.BigtableColumn; -import com.erikmafo.btviewer.model.BigtableRow; -import com.erikmafo.btviewer.model.BigtableValueConverter; -import javafx.beans.property.ReadOnlyObjectWrapper; -import javafx.collections.FXCollections; -import javafx.collections.ListChangeListener; -import javafx.collections.ObservableList; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; -import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; -import javafx.geometry.Orientation; -import javafx.scene.Node; -import javafx.scene.control.*; -import javafx.scene.input.ScrollEvent; -import javafx.scene.layout.VBox; -import java.io.IOException; -import java.util.*; -import java.util.stream.Collectors; - -/** - * Created by erikmafo on 12.12.17. - */ -public class BigtableTableView extends VBox { - - private static final String ROW_KEY = "rowkey"; - - @FXML - private Button configureRowValueTypesButton; - - @FXML - private TableView tableView; - - private BigtableValueConverter valueConverter; - - public BigtableTableView() { - - FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/bigtable_table_view.fxml")); - loader.setRoot(this); - loader.setController(this); - - try { - loader.load(); - } catch (IOException e) { - throw new RuntimeException("Unable to load fxml", e); - } - - tableView.getColumns().add(createRowKeyColumn()); - //configureRowValueTypesButton.setVisible(false); // TODO: enable this feature - } - - private ScrollBar getVerticalScrollbar() { - ScrollBar result = null; - for (Node n : tableView.lookupAll(".scroll-bar")) { - if (n instanceof ScrollBar) { - ScrollBar bar = (ScrollBar) n; - if (bar.getOrientation().equals(Orientation.VERTICAL)) { - result = bar; - } - } - } - return result; - } - - private TableColumn createRowKeyColumn() { - TableColumn tableColumn = new TableColumn<>(ROW_KEY); - tableColumn.setCellValueFactory(param -> { - BigtableRow bigtableRow = param.getValue(); - return new ReadOnlyObjectWrapper<>(bigtableRow.getRowKey()); - }); - return tableColumn; - } - - public int getMaxRows() { - ScrollBar bar = getVerticalScrollbar(); - return (int)bar.getMax(); - } - - public void setOnConfigureRowValuesTypes(EventHandler eventHandler) { - configureRowValueTypesButton.setOnAction(eventHandler); - } - - public void setOnScrollEvent(EventHandler eventHandler) { - getVerticalScrollbar().setOnScroll(eventHandler); - } - - public List getColumns() { - return tableView.getColumns() - .stream() - .filter(c -> !c.getText().equals(ROW_KEY)) - .flatMap(f -> f - .getColumns() - .stream() - .map(q -> new BigtableColumn(f.getText(), q.getText()))) - .collect(Collectors.toList()); - } - - public void clear() { - tableView.getColumns().removeIf(t -> !t.getText().equals(ROW_KEY)); - setBigTableRows(FXCollections.observableArrayList()); - } - - public void add(BigtableRow row) { - tableView.getItems().add(row); - } - - private void addColumn(String family, String qualifier) { - - var familyColumn = getFamilyTableColumn(family); - var qualifierColumn = getQualifierTableColumn(family, qualifier); - - if (familyColumn == null) { - familyColumn = new TableColumn<>(family); - familyColumn.getColumns().add(qualifierColumn); - tableView.getColumns().add(familyColumn); - } else if (familyColumn.getColumns().stream().noneMatch(c -> c.getText().equals(qualifier))) { - familyColumn.getColumns().add(qualifierColumn); - } - } - - private TableColumn getQualifierTableColumn(String family, String qualifier) { - TableColumn qualifierColumn = new TableColumn<>(qualifier); - qualifierColumn.setCellValueFactory(param -> { - var row = param.getValue(); - return new ReadOnlyObjectWrapper<>(row.getCellValue(family, qualifier, valueConverter)); - }); - return qualifierColumn; - } - - private TableColumn getFamilyTableColumn(String family) { - return tableView.getColumns() - .stream() - .filter(c -> c.getText().equals(family)) - .findFirst() - .orElse(null); - } - - private void setBigTableRows(ObservableList bigtableRows) { - tableView.setItems(bigtableRows); - bigtableRows.addListener((ListChangeListener) change -> { - while (change.next()) { - change.getAddedSubList() - .stream() - .flatMap(r -> r.getCells().stream()) - .forEach(cell -> addColumn(cell.getFamily(), cell.getQualifier())); - } - }); - } - - public void setValueConverter(BigtableValueConverter valueConverter) { - if (valueConverter.equals(this.valueConverter)) { - return; - } - - this.valueConverter = valueConverter; - var rows = this.tableView.getItems(); - clear(); - for (var row : rows) { - add(row); - } - } -} diff --git a/src/main/java/com/erikmafo/btviewer/components/BigtableTablesListView.java b/src/main/java/com/erikmafo/btviewer/components/BigtableTablesListView.java deleted file mode 100644 index 7a8c30d..0000000 --- a/src/main/java/com/erikmafo/btviewer/components/BigtableTablesListView.java +++ /dev/null @@ -1,128 +0,0 @@ -package com.erikmafo.btviewer.components; -import com.erikmafo.btviewer.events.BigtableProjectTreeItemExpanded; -import com.erikmafo.btviewer.model.BigtableInstance; -import com.erikmafo.btviewer.model.BigtableTable; -import javafx.beans.property.ReadOnlyObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; -import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; -import javafx.scene.control.*; -import javafx.scene.layout.VBox; - -import java.io.IOException; -import java.util.List; -import java.util.stream.Collectors; - -public class BigtableTablesListView extends VBox { - - @FXML - private Button addInstanceButton; - - @FXML - private TreeView treeView; - - private SimpleObjectProperty selectedTableProperty; - - private EventHandler treeItemExpandedEventHandler; - - public BigtableTablesListView(){ - var loader = new FXMLLoader(getClass().getResource("/fxml/bigtable_tables_list_view.fxml")); - loader.setRoot(this); - loader.setController(this); - try { - loader.load(); - } catch (IOException e) { - throw new RuntimeException("Unable to load fxml", e); - } - - treeView.setRoot(new TreeItem<>("projects")); - treeView.getRoot().setExpanded(true); - selectedTableProperty = new SimpleObjectProperty<>(); - treeView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { - if (countParents(newValue) == 3) { - TreeItem instanceId = newValue.getParent(); - TreeItem projectId = instanceId.getParent(); - - selectedTableProperty.set(new BigtableTable( - projectId.getValue(), instanceId.getValue(), newValue.getValue())); - } - }); - } - - private static int countParents(TreeItem treeItem) { - if (treeItem.getParent() == null) { - return 0; - } - - return 1 + countParents(treeItem.getParent()); - } - - public void setOnCreateNewBigtableInstance(EventHandler eventHandler) { - addInstanceButton.setOnAction(eventHandler); - } - - public void addBigtableInstances(List bigtableInstances) { - bigtableInstances.forEach(this::addBigtableInstance); - } - - public void addBigtableInstance(BigtableInstance instance) { - var projectTreeItem = treeView.getRoot() - .getChildren() - .stream().filter(p -> p.getValue().equals(instance.getProjectId())) - .findFirst() - .orElse(null); - - if (projectTreeItem == null) { - final TreeItem projectTreeItemFinal = new TreeItem<>(instance.getProjectId()); - - projectTreeItemFinal.expandedProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue observableValue, Boolean wasExpanded, Boolean isExpanded) { - if (isExpanded && treeItemExpandedEventHandler != null) { - List bigtableInstances = projectTreeItemFinal.getChildren() - .stream() - .map(i -> new BigtableInstance(instance.getProjectId(), i.getValue())) - .collect(Collectors.toList()); - treeItemExpandedEventHandler.handle(new BigtableProjectTreeItemExpanded(bigtableInstances)); - } - } - }); - projectTreeItem = projectTreeItemFinal; - } - - var instanceTreeItem = new TreeItem<>(instance.getInstanceId()); - projectTreeItem.getChildren().add(instanceTreeItem); - treeView.getRoot().getChildren().add(projectTreeItem); - treeView.setVisible(true); - } - - public ReadOnlyObjectProperty selectedTableProperty() { - return selectedTableProperty; - } - - public void addBigtableTables(List tables) { - - for (BigtableTable table : tables) { - treeView.getRoot() - .getChildren() - .stream() - .filter(item -> item.getValue().equals(table.getProjectId())) - .flatMap(item -> item.getChildren().stream()) - .filter(item -> item.getValue().equals(table.getInstanceId())) - .findFirst() - .ifPresent(instance -> { - if (instance.getChildren().stream().noneMatch(c -> c.getValue().equals(table.getTableId()))) { - instance.getChildren().add(new TreeItem<>(table.getTableId())); - } - }); - } - } - - public void setTreeItemExpandedEventHandler(EventHandler treeItemExpandedEventHandler) { - this.treeItemExpandedEventHandler = treeItemExpandedEventHandler; - } -} diff --git a/src/main/java/com/erikmafo/btviewer/components/BigtableViewController.java b/src/main/java/com/erikmafo/btviewer/components/BigtableViewController.java new file mode 100644 index 0000000..573759a --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/components/BigtableViewController.java @@ -0,0 +1,166 @@ +package com.erikmafo.btviewer.components; + +import com.erikmafo.btviewer.model.*; +import com.erikmafo.btviewer.services.LoadTableSettingsService; +import com.erikmafo.btviewer.services.SaveTableSettingsService; +import com.erikmafo.btviewer.util.AlertUtil; +import javafx.beans.binding.Bindings; +import javafx.beans.property.*; +import javafx.beans.value.ObservableValue; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.layout.VBox; +import org.jetbrains.annotations.NotNull; + +import javax.inject.Inject; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class BigtableViewController { + + private static final String ROW_KEY = "key"; + + @FXML + private VBox vBox; + + @FXML + private Button tableSettingsButton; + + @FXML + private TableView tableView; + + private final SimpleObjectProperty table = new SimpleObjectProperty<>(); + private final SimpleObjectProperty tableSettings = new SimpleObjectProperty<>(); + private final SimpleObjectProperty valueConverter = new SimpleObjectProperty<>(); + + private final SaveTableSettingsService saveTableSettingsService; + private final LoadTableSettingsService loadTableSettingsService; + + @Inject + public BigtableViewController( + SaveTableSettingsService saveTableSettingsService, + LoadTableSettingsService loadTableSettingsService) { + this.saveTableSettingsService = saveTableSettingsService; + this.loadTableSettingsService = loadTableSettingsService; + valueConverter.bind(Bindings.createObjectBinding(this::createValueConverter, tableSettings)); + valueConverter.addListener(this::onValueConverterChanged); + } + + @FXML + public void initialize() { + vBox.visibleProperty().bind(tableProperty().isNotNull()); + tableView.getColumns().add(createRowKeyColumn()); + tableSettings.bind(loadTableSettingsService.valueProperty()); + tableProperty().addListener((obs, prev, current) -> { + loadTableSettingsService.setTable(current); + loadTableSettingsService.restart(); + }); + } + + public void setRows(ObservableList rows) { + rows.addListener((ListChangeListener) change -> { + tableView.getColumns().clear(); + tableView.getColumns().add(createRowKeyColumn()); + while (change.next()) { + change.getAddedSubList() + .stream() + .flatMap(r -> r.getCells().stream()) + .forEach(cell -> addColumn(cell.getFamily(), cell.getQualifier())); + } + }); + tableView.setItems(rows); + } + + public SimpleObjectProperty tableProperty() { + return table; + } + + @FXML + private void handleTableSettingsButtonPressed(ActionEvent actionEvent) { + TableSettingsDialog + .displayAndAwaitResult(getColumns(), tableSettings.getValue()) + .whenComplete((configuration, throwable) -> updateTableConfiguration(table.get(), configuration)); + } + + private TableColumn createRowKeyColumn() { + TableColumn tableColumn = new TableColumn<>(ROW_KEY); + tableColumn.setCellValueFactory(param -> { + var bigtableRow = param.getValue(); + return new ReadOnlyObjectWrapper<>(bigtableRow.getRowKey()); + }); + return tableColumn; + } + + private void addColumn(String family, String qualifier) { + var familyColumn = getFamilyTableColumn(family); + var qualifierColumn = getQualifierTableColumn(family, qualifier); + + if (familyColumn == null) { + familyColumn = new TableColumn<>(family); + familyColumn.getColumns().add(qualifierColumn); + tableView.getColumns().add(familyColumn); + } else if (familyColumn.getColumns().stream().noneMatch(c -> c.getText().equals(qualifier))) { + familyColumn.getColumns().add(qualifierColumn); + } + } + + private TableColumn getQualifierTableColumn(String family, String qualifier) { + TableColumn qualifierColumn = new TableColumn<>(qualifier); + qualifierColumn.setCellValueFactory(param -> { + var cell = param.getValue().getLatestCell(family, qualifier); + return new ReadOnlyObjectWrapper<>(valueConverter.get().convert(cell)); + }); + return qualifierColumn; + } + + private TableColumn getFamilyTableColumn(String family) { + return tableView.getColumns() + .stream() + .filter(c -> c.getText().equals(family)) + .findFirst() + .orElse(null); + } + + private void updateTableConfiguration(BigtableTable table, BigtableTableSettings configuration) { + if (configuration == null) { + return; + } + saveTableConfiguration(table, configuration); + loadTableSettingsService.restart(); + } + + private void saveTableConfiguration(BigtableTable table, BigtableTableSettings configuration) { + saveTableSettingsService.setTableConfiguration(table, configuration); + saveTableSettingsService.setOnFailed(event -> AlertUtil.displayError("Failed to save table configuration", event)); + saveTableSettingsService.restart(); + } + + private List getColumns() { + return tableView.getColumns() + .stream() + .filter(c -> !c.getText().equals(ROW_KEY)) + .flatMap(c -> c + .getColumns() + .stream() + .map(q -> new BigtableColumn(c.getText(), q.getText()))) + .collect(Collectors.toList()); + } + + private void onValueConverterChanged(ObservableValue obs, BigtableValueConverter prev, BigtableValueConverter current) { + tableView.refresh(); + } + + @NotNull + private BigtableValueConverter createValueConverter() { + var settings = tableSettings.getValue(); + return settings != null ? + new BigtableValueConverter(settings.getCellDefinitions()) : + new BigtableValueConverter(Collections.emptyList()); + } +} diff --git a/src/main/java/com/erikmafo/btviewer/components/CredentialsPathDialog.java b/src/main/java/com/erikmafo/btviewer/components/CredentialsPathDialog.java new file mode 100644 index 0000000..be92d5e --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/components/CredentialsPathDialog.java @@ -0,0 +1,96 @@ +package com.erikmafo.btviewer.components; + +import com.erikmafo.btviewer.FXMLLoaderUtil; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.*; +import javafx.stage.FileChooser; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; + +public class CredentialsPathDialog extends DialogPane { + + private static final String FILE_IS_NOT_READABLE = "File is not readable"; + private static final String FILE_NOT_FOUND = "File not found"; + + @FXML + private TextField credentialsPathTextField; + + public CredentialsPathDialog() { + FXMLLoaderUtil.loadFxml("/fxml/credentials_path_dialog.fxml", this); + } + + @FXML + private void handleEditCredentialsPathAction(ActionEvent event) { + var fileChooser = new FileChooser(); + FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("json files (*.json)", "*.json"); + fileChooser.getExtensionFilters().add(extFilter); + var file = fileChooser.showOpenDialog(getScene().getWindow()); + if (file != null) + { + credentialsPathTextField.setText(file.getPath()); + } + } + + public static CompletableFuture displayAndAwaitResult(Path currentPath) { + CompletableFuture result = new CompletableFuture<>(); + Dialog dialog = new Dialog<>(); + var credentialsPathDialog = new CredentialsPathDialog(); + + if (currentPath != null) { + credentialsPathDialog.credentialsPathTextField.textProperty().setValue(currentPath.toString()); + } + + dialog.setDialogPane(credentialsPathDialog); + dialog.setResultConverter(param -> { + if (ButtonBar.ButtonData.OK_DONE.equals(param.getButtonData())) { + return credentialsPathDialog.credentialsPathTextField.textProperty().get(); + } + return null; + }); + dialog.setOnHidden(ignore -> { + var pathAsString = dialog.getResult(); + if (validatePath(pathAsString)) { + result.complete(Path.of(pathAsString)); + } + }); + dialog.show(); + + return result; + } + + private static boolean validatePath(String pathAsString) { + if (pathAsString == null) { + return false; + } + + try { + var path = Path.of(pathAsString); + if (Files.exists(path)) { + if (Files.isReadable(path)) { + return true; + } else { + showInvalidPathAlert(FILE_IS_NOT_READABLE); + } + } else { + showInvalidPathAlert(FILE_NOT_FOUND); + } + } catch (InvalidPathException e) { + showInvalidPathAlert(e.getMessage()); + } + + return false; + } + + private static void showInvalidPathAlert(String fileIsNotReadable) { + var alert = new Alert(Alert.AlertType.INFORMATION); + alert.setTitle("Invalid path"); + alert.setHeaderText("Please specify a valid path"); + alert.setContentText(fileIsNotReadable); + alert.showAndWait(); + } +} diff --git a/src/main/java/com/erikmafo/btviewer/components/QueryBoxController.java b/src/main/java/com/erikmafo/btviewer/components/QueryBoxController.java new file mode 100644 index 0000000..fb5a09d --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/components/QueryBoxController.java @@ -0,0 +1,128 @@ +package com.erikmafo.btviewer.components; + +import com.erikmafo.btviewer.model.BigtableInstance; +import com.erikmafo.btviewer.model.BigtableRow; +import com.erikmafo.btviewer.model.BigtableTable; +import com.erikmafo.btviewer.services.ReadRowsService; +import com.erikmafo.btviewer.sql.SqlParser; +import com.erikmafo.btviewer.sql.SqlQuery; +import com.erikmafo.btviewer.util.AlertUtil; +import javafx.application.Platform; + +import javafx.beans.binding.Bindings; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.ProgressBar; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import org.fxmisc.richtext.CodeArea; +import org.fxmisc.richtext.LineNumberFactory; +import org.jetbrains.annotations.Nullable; +import org.reactfx.Subscription; + +import javax.inject.Inject; +import java.time.Duration; +import java.util.regex.Pattern; + +import static com.erikmafo.btviewer.components.SyntaxHighlightingUtil.computeSyntaxHighlighting; + +public class QueryBoxController { + + private static final Pattern WHITE_SPACE = Pattern.compile("^\\s+"); + + @FXML + private Button executeQueryButton; + + @FXML + private ProgressBar progressBar; + + @FXML + private CodeArea codeArea; + + private Subscription codeAreaSubscription; + + private final ReadRowsService readRowsService; + + private final ObjectProperty instance = new SimpleObjectProperty<>(); + private final ObjectProperty table = new SimpleObjectProperty<>(); + private final ObjectProperty query = new SimpleObjectProperty<>(); + private final ObservableList queryResult = FXCollections.observableArrayList(); + + @Inject + public QueryBoxController(ReadRowsService readRowsService) { + this.readRowsService = readRowsService; + } + + @FXML + public void initialize() { + progressBar.visibleProperty().bind(readRowsService.runningProperty()); + progressBar.progressProperty().bind(readRowsService.progressProperty()); + codeArea.setParagraphGraphicFactory(LineNumberFactory.get(codeArea)); + codeAreaSubscription = codeArea + .multiPlainChanges() + .successionEnds(Duration.ofMillis(100)) + .subscribe(ignore -> codeArea.setStyleSpans(0, computeSyntaxHighlighting(codeArea.getText()))); + table.bind(Bindings.createObjectBinding(this::createTable, instance, query)); + instance.addListener(this::onInstanceChanged); + } + + public ObjectProperty tableProperty() { return table; } + + public ObservableList getQueryResult() { return queryResult; } + + public void setQuery(String sql) { + codeArea.clear(); + codeArea.replaceText(0, 0, sql); + } + + public ObjectProperty instanceProperty() { + return instance; + } + + @FXML + private void onExecuteQueryButtonPressed(ActionEvent actionEvent) { + try { + queryResult.clear(); + query.set(new SqlParser().parse(codeArea.getText())); + readRowsService.setInstance(instance.get()); + readRowsService.setQuery(query.get()); + readRowsService.setOnSucceeded(event -> queryResult.setAll(readRowsService.getValue())); + readRowsService.setOnFailed(stateEvent -> Platform.runLater(() -> AlertUtil.displayError("Failed to execute query: ", stateEvent))); + readRowsService.restart(); + } catch (Exception ex) { + AlertUtil.displayError("Invalid query", ex); + } + } + + @FXML + private void onKeyPressedInCodeArea(KeyEvent keyEvent) { + if (keyEvent.getCode() == KeyCode.ENTER) { + int caretPosition = codeArea.getCaretPosition(); + int currentParagraph = codeArea.getCurrentParagraph(); + var matcher = WHITE_SPACE.matcher(codeArea.getParagraph(currentParagraph - 1).getSegments().get(0)); + if (matcher.find()) { + Platform.runLater(() -> codeArea.insertText(caretPosition, matcher.group())); + } + } + } + + @Nullable + private BigtableTable createTable() { + var instance = this.instance.get(); + var query = this.query.get(); + if (instance != null && query != null && query.getTableName() != null) { + return new BigtableTable(instance, query.getTableName()); + } + return null; + } + + private void onInstanceChanged(ObservableValue obs, BigtableInstance prev, BigtableInstance current) { + queryResult.clear(); + } +} diff --git a/src/main/java/com/erikmafo/btviewer/components/RowSelectionView.java b/src/main/java/com/erikmafo/btviewer/components/RowSelectionView.java deleted file mode 100644 index fce5014..0000000 --- a/src/main/java/com/erikmafo/btviewer/components/RowSelectionView.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.erikmafo.btviewer.components; - -import com.erikmafo.btviewer.events.ScanTableAction; -import javafx.event.EventHandler; -import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; -import javafx.scene.control.Button; -import javafx.scene.control.ProgressBar; -import javafx.scene.control.TextField; -import javafx.scene.layout.VBox; - -import java.io.IOException; - -public class RowSelectionView extends VBox { - - @FXML - private Button scanTableButton; - - @FXML - private ProgressBar progressBar; - - @FXML - private TextField fromTextField; - - @FXML - private TextField toTextField; - - public RowSelectionView() { - FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/row_selection_view.fxml")); - loader.setRoot(this); - loader.setController(this); - - try { - loader.load(); - } catch (IOException e) { - throw new RuntimeException("Unable to load fxml", e); - } - progressBar.setVisible(false); - } - - public void setOnScanTable(EventHandler eventHandler) { - scanTableButton.setOnAction(actionEvent -> - eventHandler.handle(new ScanTableAction(fromTextField.getText(), toTextField.getText()))); - } - - public ProgressBar getProgressBar() { - return progressBar; - } -} diff --git a/src/main/java/com/erikmafo/btviewer/components/SpecifyCredentialsPathDialog.java b/src/main/java/com/erikmafo/btviewer/components/SpecifyCredentialsPathDialog.java deleted file mode 100644 index 9232476..0000000 --- a/src/main/java/com/erikmafo/btviewer/components/SpecifyCredentialsPathDialog.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.erikmafo.btviewer.components; - -import javafx.event.ActionEvent; -import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; -import javafx.scene.control.*; -import javafx.stage.FileChooser; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.InvalidPathException; -import java.nio.file.Path; -import java.util.concurrent.CompletableFuture; - -public class SpecifyCredentialsPathDialog extends DialogPane { - - @FXML - private TextField credentialsPathTextField; - - public SpecifyCredentialsPathDialog() { - FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/specify_credentials_path_dialog.fxml")); - - loader.setController(this); - loader.setRoot(this); - try { - loader.load(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - - @FXML - private void handleEditCredentialsPathAction(ActionEvent event) { - - FileChooser fileChooser = new FileChooser(); - FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("json files (*.json)", "*.json"); - fileChooser.getExtensionFilters().add(extFilter); - File file = fileChooser.showOpenDialog(getScene().getWindow()); - - if (file != null) - { - credentialsPathTextField.setText(file.getPath()); - } - - } - - public static CompletableFuture displayAndAwaitResult(Path currentPath) { - - CompletableFuture result = new CompletableFuture<>(); - - Dialog dialog = new Dialog<>(); - SpecifyCredentialsPathDialog credentialsPathDialog = new SpecifyCredentialsPathDialog(); - - if (currentPath != null) { - credentialsPathDialog.credentialsPathTextField.textProperty().setValue(currentPath.toString()); - } - - dialog.setDialogPane(credentialsPathDialog); - - dialog.setResultConverter(param -> { - if (ButtonBar.ButtonData.OK_DONE.equals(param.getButtonData())) { - return credentialsPathDialog.credentialsPathTextField.textProperty().get(); - } - return null; - }); - - dialog.setOnHidden(event1 -> { - String pathAsString = dialog.getResult(); - if (pathAsString != null) { - try { - Path path = Path.of(pathAsString); - if (Files.exists(path)) { - if (Files.isReadable(path)) { - result.complete(path); - } else { - Alert alert = new Alert(Alert.AlertType.INFORMATION); - alert.setTitle("Invalid path"); - alert.setHeaderText("Please specify a valid path"); - alert.setContentText("File is not readable"); - alert.showAndWait(); - } - } else { - Alert alert = new Alert(Alert.AlertType.INFORMATION); - alert.setTitle("Invalid path"); - alert.setHeaderText("Please specify a valid path"); - alert.setContentText("File not found"); - alert.showAndWait(); - } - } catch (InvalidPathException e) { - Alert alert = new Alert(Alert.AlertType.INFORMATION); - alert.setTitle("Invalid path"); - alert.setHeaderText("Please specify a valid path"); - alert.setContentText(e.getMessage()); - alert.showAndWait(); - } - } - }); - - dialog.show(); - - return result; - } - -} diff --git a/src/main/java/com/erikmafo/btviewer/components/SyntaxHighlightingUtil.java b/src/main/java/com/erikmafo/btviewer/components/SyntaxHighlightingUtil.java new file mode 100644 index 0000000..dbdeafc --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/components/SyntaxHighlightingUtil.java @@ -0,0 +1,55 @@ +package com.erikmafo.btviewer.components; + +import com.erikmafo.btviewer.sql.SqlToken; +import com.erikmafo.btviewer.sql.SqlTokenType; +import com.erikmafo.btviewer.sql.SqlTokenizer; +import org.fxmisc.richtext.model.StyleSpans; +import org.fxmisc.richtext.model.StyleSpansBuilder; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Collections; + +public class SyntaxHighlightingUtil { + + private static final String KEYWORD_STYLE_CLASS = "keyword"; + private static final String STRING_STYLE_CLASS = "string"; + + public static StyleSpans> computeSyntaxHighlighting(String text) { + var sqlTokenizer = new SqlTokenizer(text); + var token = sqlTokenizer.next(); + int lastHighlightEnd = 0; + StyleSpansBuilder> spansBuilder = new StyleSpansBuilder<>(); + while (token != null) { + String styleClass = getStyleClass(token); + if (styleClass != null) { + spansBuilder.add(Collections.emptyList(),token.getStart() - lastHighlightEnd); + spansBuilder.add(Collections.singleton(styleClass),token.getValue().length()); + lastHighlightEnd = token.getEnd(); + } + token = sqlTokenizer.next(); + } + spansBuilder.add(Collections.emptyList(),text.length() - lastHighlightEnd); + return spansBuilder.create(); + } + + @Nullable + private static String getStyleClass(SqlToken token) { + if (SqlTokenType.QUOTED_STRING.equals(token.getTokenType())) { + return STRING_STYLE_CLASS; + } + + switch (token.getValue().toUpperCase()) { + case "SELECT": + case "WHERE": + case "FROM": + case "LIMIT": + case "AND": + case "LIKE": + case "KEY": + return KEYWORD_STYLE_CLASS; + default: + return null; + } + } +} diff --git a/src/main/java/com/erikmafo/btviewer/components/BigtableValueTypesDialog.java b/src/main/java/com/erikmafo/btviewer/components/TableSettingsDialog.java similarity index 66% rename from src/main/java/com/erikmafo/btviewer/components/BigtableValueTypesDialog.java rename to src/main/java/com/erikmafo/btviewer/components/TableSettingsDialog.java index 0a59b88..0b2c037 100644 --- a/src/main/java/com/erikmafo/btviewer/components/BigtableValueTypesDialog.java +++ b/src/main/java/com/erikmafo/btviewer/components/TableSettingsDialog.java @@ -1,17 +1,17 @@ package com.erikmafo.btviewer.components; +import com.erikmafo.btviewer.FXMLLoaderUtil; import com.erikmafo.btviewer.model.BigtableColumn; -import com.erikmafo.btviewer.model.BigtableTableConfiguration; +import com.erikmafo.btviewer.model.BigtableTable; +import com.erikmafo.btviewer.model.BigtableTableSettings; import com.erikmafo.btviewer.model.CellDefinition; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.event.ActionEvent; import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; import javafx.scene.Node; import javafx.scene.control.*; import javafx.scene.layout.GridPane; -import java.io.IOException; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @@ -19,7 +19,47 @@ /** * Created by erikmafo on 24.12.17. */ -public class BigtableValueTypesDialog extends DialogPane { +public class TableSettingsDialog extends DialogPane { + + public static CompletableFuture displayAndAwaitResult( + List columns, + BigtableTableSettings current) { + + if (current == null) { + current = new BigtableTableSettings(); + } + + CompletableFuture future = new CompletableFuture<>(); + try { + Dialog dialog = new Dialog<>(); + TableSettingsDialog settingsDialog = new TableSettingsDialog(); + current.getCellDefinitions().forEach(settingsDialog::addSchemaRow); + columns.forEach(settingsDialog::addSchemaRow); + if (settingsDialog.observableCells.isEmpty()) { + settingsDialog.addSchemaRow(); + } + + dialog.setDialogPane(settingsDialog); + dialog.getResult(); + dialog.setResultConverter(buttonType -> { + if (ButtonBar.ButtonData.OK_DONE.equals(buttonType.getButtonData())) { + return settingsDialog.getBigtableTableConfiguration(); + } + return null; + }); + + dialog.setOnHidden(event -> { + BigtableTableSettings configuration = dialog.getResult(); + future.complete(configuration); + }); + + dialog.show(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + return future; + } @FXML private GridPane schemaGridPane; @@ -28,34 +68,55 @@ public class BigtableValueTypesDialog extends DialogPane { private int currentSchemaRow = 1; - private BigtableValueTypesDialog() { + private BigtableTable table; - FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/bigtable_value_types_dialog.fxml")); - loader.setRoot(this); - loader.setController(this); + private TableSettingsDialog() { + FXMLLoaderUtil.loadFxml("/fxml/table_settings_dialog.fxml", this); + } - try { - loader.load(); - } catch (IOException e) { - throw new RuntimeException(e); + public void onAddTableRow(ActionEvent event) { + addSchemaRow(); + } + + private void deleteRow(GridPane grid, final int row) { + Set deleteNodes = new HashSet<>(); + for (Node child : grid.getChildren()) { + // get index from child + Integer rowIndex = GridPane.getRowIndex(child); + + // handle null values for index=0 + int r = rowIndex == null ? 0 : rowIndex; + + if (r > row) { + // decrement rows for rows after the deleted row + GridPane.setRowIndex(child, r - 1); + } else if (r == row) { + // collect matching rows for deletion + deleteNodes.add(child); + } } + + // remove nodes from row + grid.getChildren().removeAll(deleteNodes); } - private BigtableTableConfiguration getBigtableTableConfiguration() { + private void setBigtableTable(BigtableTable table) { + this.table = table; + } - BigtableTableConfiguration configuration = new BigtableTableConfiguration(); - List cellDefinitionList = observableCells + private BigtableTableSettings getBigtableTableConfiguration() { + var cellDefinitionList = observableCells .stream() - .map(cell -> new CellDefinition(cell.getValueType(), cell.getFamily(), cell.getQualifier())) + .map(this::toCellDefinition) .collect(Collectors.toList()); + return new BigtableTableSettings(cellDefinitionList); + } - configuration.setCellDefinitions(cellDefinitionList); - return configuration; - + private CellDefinition toCellDefinition(ObservableCell cell) { + return new CellDefinition(cell.getValueType(), cell.getFamily(), cell.getQualifier()); } private void addSchemaRow() { - addSchemaRow(new BigtableColumn("", "")); } @@ -64,10 +125,18 @@ private void addSchemaRow(BigtableColumn column) { } private void addSchemaRow(CellDefinition cellDefinition) { - addSchemaRow(new BigtableColumn(cellDefinition.getFamily(), cellDefinition.getQualifier()), cellDefinition.getValueType()); + addSchemaRow(new BigtableColumn( + cellDefinition.getFamily(), cellDefinition.getQualifier()), cellDefinition.getValueType()); } private void addSchemaRow(BigtableColumn column, String valueType) { + var alreadyAdded = observableCells.stream().anyMatch(cell -> + cell.getFamily().equals(column.getFamily()) && + cell.getQualifier().equals(column.getQualifier())); + + if (alreadyAdded) { + return; + } ChoiceBox choiceBox = new ChoiceBox<>(); choiceBox.setValue(valueType != null ? valueType : "String"); @@ -85,32 +154,6 @@ private void addSchemaRow(BigtableColumn column, String valueType) { currentSchemaRow++; } - public void onAddTableRow(ActionEvent event) { - addSchemaRow(); - } - - static void deleteRow(GridPane grid, final int row) { - Set deleteNodes = new HashSet<>(); - for (Node child : grid.getChildren()) { - // get index from child - Integer rowIndex = GridPane.getRowIndex(child); - - // handle null values for index=0 - int r = rowIndex == null ? 0 : rowIndex; - - if (r > row) { - // decrement rows for rows after the deleted row - GridPane.setRowIndex(child, r - 1); - } else if (r == row) { - // collect matching rows for deletion - deleteNodes.add(child); - } - } - - // remove nodes from row - grid.getChildren().removeAll(deleteNodes); - } - private static class ObservableCell { private StringProperty valueType = new SimpleStringProperty(); @@ -153,43 +196,4 @@ public void setQualifier(String qualifier) { this.qualifier.set(qualifier); } } - - public static CompletableFuture displayAndAwaitResult(List columns, BigtableTableConfiguration current) { - CompletableFuture future = new CompletableFuture<>(); - - try { - Dialog dialog = new Dialog<>(); - BigtableValueTypesDialog pane = new BigtableValueTypesDialog(); - - if (columns.size() == 0) { - pane.addSchemaRow(); - } - else if (current != null) { - current.getCellDefinitions().forEach(pane::addSchemaRow); - } - else { - columns.forEach(pane::addSchemaRow); - } - - dialog.setDialogPane(pane); - dialog.getResult(); - dialog.setResultConverter(buttonType -> { - if (ButtonBar.ButtonData.OK_DONE.equals(buttonType.getButtonData())) { - return pane.getBigtableTableConfiguration(); - } - return null; - }); - - dialog.setOnHidden(event -> { - BigtableTableConfiguration configuration = dialog.getResult(); - future.complete(configuration); - }); - - dialog.show(); - } catch (Exception e) { - throw new RuntimeException(e); - } - - return future; - } } diff --git a/src/main/java/com/erikmafo/btviewer/config/AppConfig.java b/src/main/java/com/erikmafo/btviewer/config/AppConfig.java index 39585e2..45078e8 100644 --- a/src/main/java/com/erikmafo/btviewer/config/AppConfig.java +++ b/src/main/java/com/erikmafo/btviewer/config/AppConfig.java @@ -16,27 +16,20 @@ private static String getConfigName(ApplicationEnvironment environment) { } private final boolean useBigtableEmulator; - private final boolean useInMemoryTableConfigManager; - private final boolean useInMemoryInstanceManager; + private final boolean useInMemoryDatabase; @Inject public AppConfig(@Named("USE_BIGTABLE_EMULATOR") boolean useBigtableEmulator, - @Named("USE_IN_MEMORY_TABLE_CONFIG_MANAGER") boolean useInMemoryTableConfigManager, - @Named("USE_IN_MEMORY_INSTANCE_MANAGER") boolean useInMemoryInstanceManager) { + @Named("USE_IN_MEMORY_DATABASE") boolean useInMemoryDatabase) { this.useBigtableEmulator = useBigtableEmulator; - this.useInMemoryTableConfigManager = useInMemoryTableConfigManager; - this.useInMemoryInstanceManager = useInMemoryInstanceManager; + this.useInMemoryDatabase = useInMemoryDatabase; } public boolean useBigtableEmulator() { return useBigtableEmulator; } - public boolean useInMemoryTableConfigManager() { - return useInMemoryTableConfigManager; - } - - public boolean useInMemoryInstanceManager() { - return useInMemoryInstanceManager; + public boolean useInMemoryDatabase() { + return useInMemoryDatabase; } } diff --git a/src/main/java/com/erikmafo/btviewer/config/ConfigInjectionUtil.java b/src/main/java/com/erikmafo/btviewer/config/ConfigInjectionUtil.java index d7315b9..24edf0a 100644 --- a/src/main/java/com/erikmafo/btviewer/config/ConfigInjectionUtil.java +++ b/src/main/java/com/erikmafo/btviewer/config/ConfigInjectionUtil.java @@ -4,6 +4,7 @@ import com.google.inject.Guice; import com.google.inject.name.Names; +import java.io.FileNotFoundException; import java.io.IOException; import java.util.Properties; diff --git a/src/main/java/com/erikmafo/btviewer/controllers/MainController.java b/src/main/java/com/erikmafo/btviewer/controllers/MainController.java index 97e83ab..15aeafe 100644 --- a/src/main/java/com/erikmafo/btviewer/controllers/MainController.java +++ b/src/main/java/com/erikmafo/btviewer/controllers/MainController.java @@ -1,18 +1,9 @@ package com.erikmafo.btviewer.controllers; import com.erikmafo.btviewer.components.*; -import com.erikmafo.btviewer.events.ScanTableAction; -import com.erikmafo.btviewer.model.*; -import com.erikmafo.btviewer.services.*; -import javafx.beans.value.ObservableValue; -import javafx.concurrent.WorkerStateEvent; -import javafx.event.ActionEvent; +import com.erikmafo.btviewer.projectexplorer.ProjectExplorerController; +import com.erikmafo.btviewer.sql.SqlQuery; import javafx.fxml.FXML; -import javafx.scene.control.Alert; -import javafx.scene.control.ButtonType; -import javafx.scene.control.Label; - -import javax.inject.Inject; /** * Created by erikmafo on 23.12.17. @@ -20,162 +11,22 @@ public class MainController { @FXML - private RowSelectionView rowSelectionView; + private QueryBoxController queryBoxController; @FXML - private BigtableTablesListView tablesListView; + private ProjectExplorerController projectExplorerController; @FXML - private Label tableNameLabel; + private BigtableViewController bigtableViewController; @FXML - private BigtableTableView bigtableTableView; - - private final SaveCredentialsPathService saveCredentialsPathService; - private final SaveInstancesService saveInstancesService; - private final LoadInstancesService loadInstancesService; - private final SaveTableConfigurationService saveTableConfigurationService; - private final LoadTableConfigurationService loadTableConfigurationService; - private final ReadRowsService readRowsService; - private final ListTablesService listTablesService; - - @Inject - public MainController( - SaveCredentialsPathService saveCredentialsPathService, - SaveInstancesService saveInstancesService, - LoadInstancesService loadInstancesService, - SaveTableConfigurationService saveTableConfigurationService, - LoadTableConfigurationService loadTableConfigurationService, - ReadRowsService readRowsService, - ListTablesService listTablesService) { - this.saveCredentialsPathService = saveCredentialsPathService; - this.saveInstancesService = saveInstancesService; - this.loadInstancesService = loadInstancesService; - this.saveTableConfigurationService = saveTableConfigurationService; - this.loadTableConfigurationService = loadTableConfigurationService; - this.readRowsService = readRowsService; - this.listTablesService = listTablesService; - } - - public void initialize() { - rowSelectionView.setVisible(false); - bigtableTableView.setVisible(false); - tableNameLabel.setVisible(false); - bigtableTableView.setOnConfigureRowValuesTypes(this::onConfigureRowValueTypes); - tablesListView.setOnCreateNewBigtableInstance(this::onAddNewBigtableInstance); - tablesListView.selectedTableProperty().addListener(this::onBigtableTableSelected); - tablesListView.setTreeItemExpandedEventHandler(event -> - event.getBigtableInstances().forEach(MainController.this::listBigtableTables)); - rowSelectionView.setOnScanTable(this::onScanTableAction); - loadBigtableInstances(); - } - - private void loadBigtableInstances() { - loadInstancesService.setOnSucceeded(stateEvent -> - tablesListView.addBigtableInstances(loadInstancesService.getValue())); - loadInstancesService.setOnFailed(stateEvent -> displayErrorInfo("Failed to load instances", stateEvent)); - loadInstancesService.restart(); - } - - private void onAddNewBigtableInstance(ActionEvent event) { - BigtableInstanceDialog.displayAndAwaitResult() - .whenComplete((instance, throwable) -> { - saveInstance(instance); - tablesListView.addBigtableInstance(instance); - listBigtableTables(instance); - }); - } - - private void saveInstance(BigtableInstance instance) { - saveInstancesService.addInstance(instance); - saveInstancesService.setOnFailed(stateEvent -> displayErrorInfo("Unable to save instance", stateEvent)); - saveInstancesService.restart(); - } - - private void listBigtableTables(BigtableInstance instance) { - listTablesService.setInstance(instance); - listTablesService.setOnSucceeded(workerStateEvent -> - tablesListView.addBigtableTables(listTablesService.getValue())); - listTablesService.setOnFailed(stateEvent -> displayErrorInfo("Unable to list tables", stateEvent)); - listTablesService.restart(); - } - - private void onScanTableAction(ScanTableAction actionEvent) { - bigtableTableView.clear(); - var currentTable = tablesListView.selectedTableProperty().get(); - loadTableConfiguration(currentTable); - var request = new BigtableReadRequestBuilder() - .setTable(currentTable) - .setRowRange(new BigtableRowRange(actionEvent.getFrom(), actionEvent.getTo())) - .build(); - readBigtableRows(request); - } - - private void loadTableConfiguration(BigtableTable currentTable) { - loadTableConfigurationService.setTable(currentTable); - loadTableConfigurationService.setOnSucceeded(event -> bigtableTableView.setValueConverter( - new BigtableValueConverter(loadTableConfigurationService.getValue().getCellDefinitions()))); - loadTableConfigurationService.setOnFailed(event -> displayErrorInfo("Unable to load table configuration", event)); - loadTableConfigurationService.restart(); - } - - private void onConfigureRowValueTypes(ActionEvent event) { - var table = tablesListView.selectedTableProperty().get(); - loadTableConfigurationService.setTable(table); - loadTableConfigurationService.setOnSucceeded(e -> BigtableValueTypesDialog - .displayAndAwaitResult(bigtableTableView.getColumns(), loadTableConfigurationService.getValue()) - .whenComplete((configuration, throwable) -> updateTableConfiguration(table, configuration)) - ); - loadTableConfigurationService.setOnFailed(e -> BigtableValueTypesDialog - .displayAndAwaitResult(bigtableTableView.getColumns(), null) - .whenComplete((configuration, throwable) -> updateTableConfiguration(table, configuration)) - ); - loadTableConfigurationService.restart(); - } - - private void updateTableConfiguration(BigtableTable table, BigtableTableConfiguration configuration) { - bigtableTableView.setValueConverter(new BigtableValueConverter(configuration.getCellDefinitions())); - saveTableConfiguration(table, configuration); - } - - private void saveTableConfiguration(BigtableTable table, BigtableTableConfiguration configuration) { - saveTableConfigurationService.setTableConfiguration(table, configuration); - saveTableConfigurationService.setOnFailed(event -> displayErrorInfo("Failed to save table configuration", event)); - saveTableConfigurationService.restart(); - } - - private void displayErrorInfo(String errorText, WorkerStateEvent event) { - var exception = event.getSource().getException(); - var alert = new Alert(Alert.AlertType.ERROR, errorText + " " + exception.getLocalizedMessage(), ButtonType.CLOSE); - alert.showAndWait(); - } - - private void onBigtableTableSelected(ObservableValue observable, BigtableTable oldValue, BigtableTable newValue) { - bigtableTableView.clear(); - tableNameLabel.setText(newValue.getSimpleName()); - tableNameLabel.setVisible(true); - rowSelectionView.setVisible(true); - readBigtableRows(createReadRequest(newValue)); - } - - private BigtableReadRequest createReadRequest(BigtableTable newValue) { - return new BigtableReadRequestBuilder() - .setTable(newValue) - .setRowRange(BigtableRowRange.DEFAULT) - .build(); - } - - private void readBigtableRows(BigtableReadRequest request) { - readRowsService.setReadRequest(request); - readRowsService.setOnSucceeded(workerStateEvent -> { - bigtableTableView.setVisible(true); - rowSelectionView.getProgressBar().setVisible(false); - loadTableConfiguration(request.getBigtableTable()); - readRowsService.getValue().forEach(row -> bigtableTableView.add(row)); - }); - readRowsService.setOnFailed(stateEvent -> displayErrorInfo("Unable to read bigtable rows", stateEvent)); - rowSelectionView.getProgressBar().setVisible(true); - rowSelectionView.getProgressBar().progressProperty().bind(readRowsService.progressProperty()); - readRowsService.restart(); + private void initialize() { + bigtableViewController.tableProperty().bind(queryBoxController.tableProperty()); + bigtableViewController.setRows(queryBoxController.getQueryResult()); + queryBoxController.instanceProperty().bind(projectExplorerController.selectedInstanceProperty()); + projectExplorerController + .selectedTableProperty() + .addListener((obs, prev, current) -> + queryBoxController.setQuery(SqlQuery.getDefaultSql(current.getTableId()))); } } diff --git a/src/main/java/com/erikmafo/btviewer/controllers/MenuBarController.java b/src/main/java/com/erikmafo/btviewer/controllers/MenuBarController.java index 6c8331c..4d45bd5 100644 --- a/src/main/java/com/erikmafo/btviewer/controllers/MenuBarController.java +++ b/src/main/java/com/erikmafo/btviewer/controllers/MenuBarController.java @@ -1,6 +1,6 @@ package com.erikmafo.btviewer.controllers; -import com.erikmafo.btviewer.components.SpecifyCredentialsPathDialog; +import com.erikmafo.btviewer.components.CredentialsPathDialog; import com.erikmafo.btviewer.services.LoadCredentialsPathService; import com.erikmafo.btviewer.services.SaveCredentialsPathService; import com.sun.javafx.PlatformUtil; @@ -49,7 +49,7 @@ public void onManageCredentialsAction(ActionEvent event) { } private void displaySpecifyCredentialsDialog(Path currentPath) { - SpecifyCredentialsPathDialog.displayAndAwaitResult(currentPath) + CredentialsPathDialog.displayAndAwaitResult(currentPath) .whenComplete(this::onCredentialsPathDialogComplete); } diff --git a/src/main/java/com/erikmafo/btviewer/events/BigtableProjectTreeItemExpanded.java b/src/main/java/com/erikmafo/btviewer/events/BigtableProjectTreeItemExpanded.java deleted file mode 100644 index 9478ca1..0000000 --- a/src/main/java/com/erikmafo/btviewer/events/BigtableProjectTreeItemExpanded.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.erikmafo.btviewer.events; - -import com.erikmafo.btviewer.model.BigtableInstance; -import javafx.event.Event; -import javafx.event.EventType; - -import java.util.List; - -public class BigtableProjectTreeItemExpanded extends Event { - - public static final EventType PROJECT_TREE_ITEM_EXPANDED_EVENT_TYPE = new EventType<>(EventType.ROOT, "BigtableProjectTreeItemExpanded"); - - private final List bigtableInstances; - - public BigtableProjectTreeItemExpanded(List instances) { - super(PROJECT_TREE_ITEM_EXPANDED_EVENT_TYPE); - bigtableInstances = instances; - } - - public List getBigtableInstances() { - return bigtableInstances; - } -} diff --git a/src/main/java/com/erikmafo/btviewer/events/ScanTableAction.java b/src/main/java/com/erikmafo/btviewer/events/ScanTableAction.java deleted file mode 100644 index bdb6d4a..0000000 --- a/src/main/java/com/erikmafo/btviewer/events/ScanTableAction.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.erikmafo.btviewer.events; -import javafx.event.Event; -import javafx.event.EventType; - -public class ScanTableAction extends Event { - - public static final EventType SCAN_TABLE = new EventType<>(EventType.ROOT, "ScanTableAction"); - - private final String from; - private final String to; - - public ScanTableAction(String from, String to) { - super(SCAN_TABLE); - this.from = from; - this.to = to; - } - - public String getFrom() { - return from; - } - - public String getTo() { - return to; - } -} diff --git a/src/main/java/com/erikmafo/btviewer/exceptions/InvalidCredentialsRecordException.java b/src/main/java/com/erikmafo/btviewer/exceptions/InvalidCredentialsRecordException.java deleted file mode 100644 index 804fee3..0000000 --- a/src/main/java/com/erikmafo/btviewer/exceptions/InvalidCredentialsRecordException.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.erikmafo.btviewer.exceptions; - -public class InvalidCredentialsRecordException extends Exception { - public InvalidCredentialsRecordException() { - } - - public InvalidCredentialsRecordException(String message) { - super(message); - } - - public InvalidCredentialsRecordException(String message, Throwable cause) { - super(message, cause); - } - - public InvalidCredentialsRecordException(Throwable cause) { - super(cause); - } - - public InvalidCredentialsRecordException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } -} diff --git a/src/main/java/com/erikmafo/btviewer/model/BigtableCell.java b/src/main/java/com/erikmafo/btviewer/model/BigtableCell.java index c28a4fd..4ce2bcc 100644 --- a/src/main/java/com/erikmafo/btviewer/model/BigtableCell.java +++ b/src/main/java/com/erikmafo/btviewer/model/BigtableCell.java @@ -7,11 +7,13 @@ public class BigtableCell { private final String family; private final String qualifier; private final ByteString bytes; + private final long timestamp; - public BigtableCell(String family, String qualifier, ByteString bytes) { + public BigtableCell(String family, String qualifier, ByteString bytes, long timestamp) { this.family = family; this.qualifier = qualifier; this.bytes = bytes; + this.timestamp = timestamp; } public String getFamily() { @@ -27,4 +29,6 @@ public String getQualifier() { public byte[] getBytes() { return bytes.toByteArray(); } + + public long getTimestamp() { return timestamp; } } diff --git a/src/main/java/com/erikmafo/btviewer/model/BigtableInstance.java b/src/main/java/com/erikmafo/btviewer/model/BigtableInstance.java index 21a1b34..176e298 100644 --- a/src/main/java/com/erikmafo/btviewer/model/BigtableInstance.java +++ b/src/main/java/com/erikmafo/btviewer/model/BigtableInstance.java @@ -10,11 +10,9 @@ public class BigtableInstance { public BigtableInstance() {} - public BigtableInstance(String name) - { - String[] parts = name.split("/"); - this.projectId = parts[1]; - this.instanceId = parts[3]; + public BigtableInstance(BigtableInstance instance) { + this.projectId = instance.getProjectId(); + this.instanceId = instance.getInstanceId(); } public BigtableInstance(String projectId, String instanceId) { diff --git a/src/main/java/com/erikmafo/btviewer/model/BigtableReadRequest.java b/src/main/java/com/erikmafo/btviewer/model/BigtableReadRequest.java index 08d7d25..3f6f426 100644 --- a/src/main/java/com/erikmafo/btviewer/model/BigtableReadRequest.java +++ b/src/main/java/com/erikmafo/btviewer/model/BigtableReadRequest.java @@ -1,27 +1,28 @@ package com.erikmafo.btviewer.model; - -import java.nio.file.Path; +import com.google.cloud.bigtable.data.v2.models.Query; public class BigtableReadRequest { - private final BigtableTable bigtableTable; - private final Path credentialsPath; - private final BigtableRowRange scan; + private final BigtableInstance instance; + private final Query query; + private final long limit; - BigtableReadRequest(BigtableTable bigtableTable, Path credentialsPath, BigtableRowRange scan) { - this.bigtableTable = bigtableTable; - this.credentialsPath = credentialsPath; - this.scan = scan; + BigtableReadRequest(BigtableInstance instance, Query query, long limit) { + this.instance = instance; + this.query = query; + this.limit = limit; } - public Path getCredentialsPath() { - return credentialsPath; + public BigtableInstance getInstance() { + return instance; } - public BigtableRowRange getScan() { - return scan; + public Query getQuery() { + return query; } - public BigtableTable getBigtableTable() { return bigtableTable; } + public long getLimit() { + return limit; + } } diff --git a/src/main/java/com/erikmafo/btviewer/model/BigtableReadRequestBuilder.java b/src/main/java/com/erikmafo/btviewer/model/BigtableReadRequestBuilder.java index c6ff875..e437c95 100644 --- a/src/main/java/com/erikmafo/btviewer/model/BigtableReadRequestBuilder.java +++ b/src/main/java/com/erikmafo/btviewer/model/BigtableReadRequestBuilder.java @@ -1,29 +1,29 @@ package com.erikmafo.btviewer.model; -import java.nio.file.Path; +import com.google.cloud.bigtable.data.v2.models.Query; public class BigtableReadRequestBuilder { - private BigtableTable bigtableTable; - private Path credentialsPath; - private BigtableRowRange scan; + private BigtableInstance instance; + private Query query; + private long limit; - public BigtableReadRequestBuilder setTable(BigtableTable bigtableTable) { - this.bigtableTable = bigtableTable; + public BigtableReadRequestBuilder setInstance(BigtableInstance instance) { + this.instance = instance; return this; } - public BigtableReadRequestBuilder setCredentialsPath(Path credentialsPath) { - this.credentialsPath = credentialsPath; + public BigtableReadRequestBuilder setQuery(Query query) { + this.query = query; return this; } - public BigtableReadRequestBuilder setRowRange(BigtableRowRange scan) { - this.scan = scan; + public BigtableReadRequestBuilder setLimit(long limit) { + this.limit = limit; return this; } public BigtableReadRequest build() { - return new BigtableReadRequest(bigtableTable, credentialsPath, scan); + return new BigtableReadRequest(instance, query, limit); } } \ No newline at end of file diff --git a/src/main/java/com/erikmafo/btviewer/model/BigtableRow.java b/src/main/java/com/erikmafo/btviewer/model/BigtableRow.java index 4e7d1be..4b1d147 100644 --- a/src/main/java/com/erikmafo/btviewer/model/BigtableRow.java +++ b/src/main/java/com/erikmafo/btviewer/model/BigtableRow.java @@ -23,12 +23,21 @@ public List getCells() { return cells; } + public BigtableCell getLatestCell(String family, String qualifier) { + return cells.stream() + .filter(c -> family.equals(c.getFamily()) && qualifier.equals(c.getQualifier())) + .sorted(Comparator.comparingLong(BigtableCell::getTimestamp)) + .reduce((c1, c2) -> c2) + .orElse(null); + } + public Object getCellValue(String family, String qualifier, BigtableValueConverter converter) { BigtableCell cell = cells .stream() .filter(c -> family.equals(c.getFamily()) && qualifier.equals(c.getQualifier())) - .findFirst() + .sorted(Comparator.comparingLong(BigtableCell::getTimestamp)) + .reduce((c1, c2) -> c2) .orElse(null); if (cell == null) { diff --git a/src/main/java/com/erikmafo/btviewer/model/BigtableRowRange.java b/src/main/java/com/erikmafo/btviewer/model/BigtableRowRange.java index 81f7aaf..9621a67 100644 --- a/src/main/java/com/erikmafo/btviewer/model/BigtableRowRange.java +++ b/src/main/java/com/erikmafo/btviewer/model/BigtableRowRange.java @@ -5,20 +5,14 @@ */ public class BigtableRowRange { - public static BigtableRowRange DEFAULT = new BigtableRowRange("", "~", 1000); + public static BigtableRowRange DEFAULT = new BigtableRowRange("", "~"); private final String from; private final String to; - private final int maxRows; public BigtableRowRange(String from, String to) { - this(from, to, 1000); - } - - public BigtableRowRange(String from, String to, int maxRows) { this.from = from; this.to = to; - this.maxRows = maxRows; } public String getFrom() { @@ -28,8 +22,4 @@ public String getFrom() { public String getTo() { return to; } - - public int getMaxRows() { - return maxRows; - } } diff --git a/src/main/java/com/erikmafo/btviewer/model/BigtableTable.java b/src/main/java/com/erikmafo/btviewer/model/BigtableTable.java index 98e05d1..39372a9 100644 --- a/src/main/java/com/erikmafo/btviewer/model/BigtableTable.java +++ b/src/main/java/com/erikmafo/btviewer/model/BigtableTable.java @@ -23,6 +23,12 @@ public BigtableTable(String projectId, String instanceId, String tableId) { this.tableId = tableId; } + public BigtableTable(BigtableInstance instance, String tableId) { + this.projectId = instance.getProjectId(); + this.instanceId = instance.getInstanceId(); + this.tableId = tableId; + } + public String getName() { return String.format("projects/%s/instances/%s/tables/%s", projectId, instanceId, tableId); } diff --git a/src/main/java/com/erikmafo/btviewer/model/BigtableTableConfiguration.java b/src/main/java/com/erikmafo/btviewer/model/BigtableTableSettings.java similarity index 72% rename from src/main/java/com/erikmafo/btviewer/model/BigtableTableConfiguration.java rename to src/main/java/com/erikmafo/btviewer/model/BigtableTableSettings.java index d6314cf..ae0cbac 100644 --- a/src/main/java/com/erikmafo/btviewer/model/BigtableTableConfiguration.java +++ b/src/main/java/com/erikmafo/btviewer/model/BigtableTableSettings.java @@ -1,12 +1,18 @@ package com.erikmafo.btviewer.model; +import java.util.Collections; import java.util.List; -public class BigtableTableConfiguration { +public class BigtableTableSettings { private List cellDefinitions; - public BigtableTableConfiguration() { + public BigtableTableSettings() { + cellDefinitions = Collections.emptyList(); + } + + public BigtableTableSettings(List cellDefinitions) { + this.cellDefinitions = cellDefinitions; } public List getCellDefinitions() { diff --git a/src/main/java/com/erikmafo/btviewer/model/BigtableValueConverter.java b/src/main/java/com/erikmafo/btviewer/model/BigtableValueConverter.java index 52ade4a..1085901 100644 --- a/src/main/java/com/erikmafo/btviewer/model/BigtableValueConverter.java +++ b/src/main/java/com/erikmafo/btviewer/model/BigtableValueConverter.java @@ -2,6 +2,7 @@ import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; +import java.util.LinkedList; import java.util.List; import java.util.Objects; @@ -10,6 +11,14 @@ */ public class BigtableValueConverter { + public static BigtableValueConverter from(BigtableTableSettings config) { + if (config == null) { + return new BigtableValueConverter(new LinkedList<>()); + } + + return new BigtableValueConverter(config.getCellDefinitions()); + } + private final List cellDefinitions; public BigtableValueConverter(List cellDefinitions) { @@ -20,15 +29,19 @@ public List getCellDefinitions() { return cellDefinitions; } - public Object convert(BigtableCell bigtableCell) { - CellDefinition cellDefinition = cellDefinitions.stream() - .filter(c -> c.getFamily().equals(bigtableCell.getFamily()) - && c.getQualifier().equals(bigtableCell.getQualifier())) + public Object convert(BigtableCell cell) { + if (cell == null) { + return null; + } + + var cellDefinition = cellDefinitions.stream() + .filter(c -> c.getFamily().equals(cell.getFamily()) + && c.getQualifier().equals(cell.getQualifier())) .findFirst() - .orElse(new CellDefinition("string", bigtableCell.getFamily(), bigtableCell.getQualifier())); + .orElse(new CellDefinition("string", cell.getFamily(), cell.getQualifier())); try { - return convertUsingValueType(bigtableCell, cellDefinition.getValueType()); + return convertUsingValueType(cell, cellDefinition.getValueType()); } catch (BufferUnderflowException ex) { return String.format("not a %s", cellDefinition.getValueType()); } diff --git a/src/main/java/com/erikmafo/btviewer/model/ByteStringConverterImpl.java b/src/main/java/com/erikmafo/btviewer/model/ByteStringConverterImpl.java new file mode 100644 index 0000000..7b2dfd9 --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/model/ByteStringConverterImpl.java @@ -0,0 +1,51 @@ +package com.erikmafo.btviewer.model; + +import com.erikmafo.btviewer.sql.ByteStringConverter; +import com.erikmafo.btviewer.sql.Field; +import com.erikmafo.btviewer.sql.Value; +import com.erikmafo.btviewer.util.ByteStringConverterUtil; +import com.google.protobuf.ByteString; + +import java.util.Collections; +import java.util.List; + +public class ByteStringConverterImpl implements ByteStringConverter { + private final List cellDefinitions; + + public ByteStringConverterImpl(BigtableTableSettings config) { + this(config.getCellDefinitions() != null ? config.getCellDefinitions() : Collections.emptyList()); + } + + public ByteStringConverterImpl(List cellDefinitions) { + this.cellDefinitions = cellDefinitions; + } + + @Override + public ByteString toByteString(Field field, Value value) { + var valueType = cellDefinitions.stream() + .filter(c -> c.getFamily().equals(field.getFamily())) + .filter(c -> c.getQualifier().equals(field.getQualifier())) + .map(CellDefinition::getValueType) + .findFirst() + .orElse("STRING"); + + ByteString byteString; + switch (valueType.toUpperCase()) { + case "STRING": + byteString = ByteStringConverterUtil.toByteString(value.asString()); + break; + case "DOUBLE": + byteString = ByteStringConverterUtil.toByteString(value.asDouble()); + break; + case "FLOAT": + byteString = ByteStringConverterUtil.toByteString(value.asFloat()); + break; + case "INTEGER": + byteString = ByteStringConverterUtil.toByteString(value.asInt()); + break; + default: throw new AssertionError(); + } + + return byteString; + } +} diff --git a/src/main/java/com/erikmafo/btviewer/projectexplorer/InstanceTreeItem.java b/src/main/java/com/erikmafo/btviewer/projectexplorer/InstanceTreeItem.java new file mode 100644 index 0000000..41ebfd4 --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/projectexplorer/InstanceTreeItem.java @@ -0,0 +1,65 @@ +package com.erikmafo.btviewer.projectexplorer; + +import com.erikmafo.btviewer.services.ListTablesService; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.scene.control.TreeItem; + +import javax.inject.Inject; +import java.util.stream.Collectors; + +public class InstanceTreeItem extends TreeItem { + + private final ListTablesService listTablesService; + private final BooleanProperty loadingProperty; + + private boolean loadedChildren; + + @Inject + public InstanceTreeItem(ListTablesService listTablesService) { + this.loadingProperty = new SimpleBooleanProperty(false); + this.listTablesService = listTablesService; + this.expandedProperty().addListener((observable, prev, isExpanded) -> { + if (isExpanded && !loadedChildren && !listTablesService.isRunning()) { + loadChildren(); + } + }); + } + + @Override + public boolean isLeaf() { + return false; + } + + public ReadOnlyBooleanProperty loadingProperty() { + return loadingProperty; + } + + private void loadChildren() { + listTablesService.setInstance(getValue().toInstance()); + loadingProperty.setValue(true); + listTablesService.setOnSucceeded(event -> { + loadingProperty.set(false); + var children = listTablesService + .getValue() + .stream() + .map(TreeItemData::new) + .map(this::createTableTreeItem) + .collect(Collectors.toList()); + getChildren().setAll(children); + loadedChildren = true; + }); + listTablesService.setOnFailed(event -> { + loadingProperty.set(false); + // TODO: show error + }); + listTablesService.restart(); + } + + private TableTreeItem createTableTreeItem(TreeItemData treeItemData) { + var tableTreeItem = new TableTreeItem(); + tableTreeItem.setValue(treeItemData); + return tableTreeItem; + } +} diff --git a/src/main/java/com/erikmafo/btviewer/projectexplorer/ProjectExplorerController.java b/src/main/java/com/erikmafo/btviewer/projectexplorer/ProjectExplorerController.java new file mode 100644 index 0000000..d687964 --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/projectexplorer/ProjectExplorerController.java @@ -0,0 +1,123 @@ +package com.erikmafo.btviewer.projectexplorer; +import com.erikmafo.btviewer.components.AddInstanceDialog; +import com.erikmafo.btviewer.model.BigtableInstance; +import com.erikmafo.btviewer.model.BigtableTable; +import com.erikmafo.btviewer.services.RemoveProjectService; +import com.erikmafo.btviewer.services.SaveInstanceService; +import com.erikmafo.btviewer.util.AlertUtil; +import com.google.inject.Provider; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.*; + +import javax.inject.Inject; + +public class ProjectExplorerController { + + @FXML + private Button addInstanceButton; + + @FXML + private TreeView treeView; + + private final Provider rootTreeItemProvider; + + private final SimpleObjectProperty selectedTableProperty; + private final SimpleObjectProperty selectedInstanceProperty; + + private final SaveInstanceService saveInstanceService; + private final RemoveProjectService removeProjectService; + + @Inject + public ProjectExplorerController( + Provider rootTreeItemProvider, + SaveInstanceService saveInstanceService, + RemoveProjectService removeProjectService) { + this.rootTreeItemProvider = rootTreeItemProvider; + this.saveInstanceService = saveInstanceService; + this.removeProjectService = removeProjectService; + this.selectedTableProperty = new SimpleObjectProperty<>(); + this.selectedInstanceProperty = new SimpleObjectProperty<>(); + } + + public void initialize() { + treeView.setRoot(rootTreeItemProvider.get()); + treeView.setCellFactory(tableInfoTreeView -> new TreeCell<>() { + @Override + protected void updateItem(TreeItemData item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { + setText(null); + setGraphic(null); + setContextMenu(null); + } else { + setContextMenu(createContextMenu(item)); + setText(item.getDisplayName()); + } + } + }); + treeView.getSelectionModel() + .selectedItemProperty() + .addListener((observable, prevSelected, selectedItem) -> { + var selected = selectedItem.getValue(); + if (selected.isTable()) { + selectedInstanceProperty.set(selected.toInstance()); + selectedTableProperty.set(selected.toTable()); + } else if (selected.isInstance()) { + selectedInstanceProperty.set(selected.toInstance()); + } + }); + treeView.setVisible(true); + addInstanceButton.setOnAction(this::handleAddInstanceAction); + } + + public ReadOnlyObjectProperty selectedInstanceProperty() { + return selectedInstanceProperty; + } + + public ReadOnlyObjectProperty selectedTableProperty() { + return selectedTableProperty; + } + + public ContextMenu createContextMenu(TreeItemData item){ + ContextMenu menu = null; + if (item.isProject()) { + var addInstance = new MenuItem("Add instance"); + addInstance.setOnAction(actionEvent -> + AddInstanceDialog + .displayAndAwaitResult(item.getProjectId()) + .whenComplete(this::handleAddInstanceResult)); + var removeProject = new MenuItem("Remove"); + removeProject.setOnAction(actionEvent -> { + removeProjectService.setProjectId(item.getProjectId()); + removeProjectService.setOnSucceeded(event -> ((RootTreeItem)treeView.getRoot()).removeProject((item.getProjectId()))); + removeProjectService.setOnFailed(event -> AlertUtil.displayError("Unable to remove project", event)); + removeProjectService.restart(); + }); + + menu = new ContextMenu(addInstance, removeProject); + } + + return menu; + } + + private void handleAddInstanceAction(ActionEvent ignore) { + AddInstanceDialog.displayAndAwaitResult().whenComplete(this::handleAddInstanceResult); + } + + private void handleAddInstanceResult(BigtableInstance instance, Throwable throwable) { + if (instance == null) { + return; + } + saveInstanceService.setInstance(instance); + saveInstanceService.setOnFailed(event -> AlertUtil.displayError("Unable to save instance", event)); + saveInstanceService.setOnSucceeded(event -> reloadOrAddProject(instance.getProjectId())); + saveInstanceService.restart(); + } + + private void reloadOrAddProject(String projectId) { + ((RootTreeItem)treeView.getRoot()).reloadOrAddProject(projectId); + } +} diff --git a/src/main/java/com/erikmafo/btviewer/projectexplorer/ProjectItemMenuController.java b/src/main/java/com/erikmafo/btviewer/projectexplorer/ProjectItemMenuController.java new file mode 100644 index 0000000..59cc29a --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/projectexplorer/ProjectItemMenuController.java @@ -0,0 +1,51 @@ +package com.erikmafo.btviewer.projectexplorer; + +import com.erikmafo.btviewer.services.RemoveProjectService; +import com.erikmafo.btviewer.services.SaveInstanceService; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.MenuItem; + +import javax.inject.Inject; + +public class ProjectItemMenuController { + + @FXML + private MenuItem addInstance; + + @FXML + private MenuItem removeProject; + + private String projectId; + + private final RemoveProjectService removeProjectService; + private final SaveInstanceService saveInstanceService; + + @Inject + public ProjectItemMenuController( + RemoveProjectService removeProjectService, + SaveInstanceService saveInstanceService) { + this.removeProjectService = removeProjectService; + this.saveInstanceService = saveInstanceService; + } + + @FXML + public void initialize() { + + } + + public void setProjectId(String projectId) { + this.projectId = projectId; + } + + @FXML + public void handleRemoveProject(ActionEvent actionEvent) { + removeProjectService.setProjectId(projectId); + removeProjectService.restart(); + } + + @FXML + public void handleAddInstance(ActionEvent actionEvent) { + + } +} diff --git a/src/main/java/com/erikmafo/btviewer/projectexplorer/ProjectTreeItem.java b/src/main/java/com/erikmafo/btviewer/projectexplorer/ProjectTreeItem.java new file mode 100644 index 0000000..6cf5265 --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/projectexplorer/ProjectTreeItem.java @@ -0,0 +1,59 @@ +package com.erikmafo.btviewer.projectexplorer; + +import com.erikmafo.btviewer.services.LoadInstancesService; +import com.erikmafo.btviewer.util.AlertUtil; +import javafx.concurrent.WorkerStateEvent; +import javafx.scene.control.TreeItem; + +import javax.inject.Inject; +import javax.inject.Provider; +import java.util.stream.Collectors; + +public class ProjectTreeItem extends TreeItem { + + private final LoadInstancesService loadInstancesService; + private final Provider instanceTreeItemProvider; + + private boolean loadedChildren; + + @Inject + public ProjectTreeItem(LoadInstancesService loadInstancesService, + Provider instanceTreeItemProvider) { + this.loadInstancesService = loadInstancesService; + this.instanceTreeItemProvider = instanceTreeItemProvider; + expandedProperty().addListener((observable, prev, isExpanded) -> { + if (isExpanded && !loadedChildren && !loadInstancesService.isRunning()) { + loadChildren(); + } + }); + } + + @Override + public boolean isLeaf() { + return false; + } + + public void loadChildren() { + loadInstancesService.setProjectId(getValue().getProjectId()); + loadInstancesService.setOnSucceeded(this::onLoadInstancesSucceeded); + loadInstancesService.setOnFailed(event -> AlertUtil.displayError("Unable to load instances", event)); + loadInstancesService.restart(); + } + + private void onLoadInstancesSucceeded(WorkerStateEvent event) { + var children = loadInstancesService + .getValue() + .stream() + .map(TreeItemData::new) + .map(this::createChild) + .collect(Collectors.toList()); + getChildren().setAll(children); + loadedChildren = true; + } + + private InstanceTreeItem createChild(TreeItemData treeItemData) { + var instanceItem = instanceTreeItemProvider.get(); + instanceItem.setValue(treeItemData); + return instanceItem; + } +} diff --git a/src/main/java/com/erikmafo/btviewer/projectexplorer/RootTreeItem.java b/src/main/java/com/erikmafo/btviewer/projectexplorer/RootTreeItem.java new file mode 100644 index 0000000..4516ba3 --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/projectexplorer/RootTreeItem.java @@ -0,0 +1,65 @@ +package com.erikmafo.btviewer.projectexplorer; + +import com.erikmafo.btviewer.services.LoadProjectsService; +import com.erikmafo.btviewer.util.AlertUtil; +import com.google.inject.Provider; +import javafx.scene.control.TreeItem; + +import javax.inject.Inject; +import java.util.stream.Collectors; + +public class RootTreeItem extends TreeItem { + + private final LoadProjectsService loadProjectsService; + private final Provider projectTreeItemProvider; + + @Inject + public RootTreeItem( + LoadProjectsService loadProjectsService, + Provider projectTreeItemProvider) { + this.projectTreeItemProvider = projectTreeItemProvider; + this.loadProjectsService = loadProjectsService; + loadChildren(loadProjectsService); + setExpanded(true); + setValue(new TreeItemData()); + } + + private void loadChildren(LoadProjectsService loadProjectsService) { + this.loadProjectsService.setOnSucceeded(event -> { + var children = loadProjectsService + .getValue() + .stream() + .map(TreeItemData::new) + .map(this::createProjectTreeItem) + .collect(Collectors.toList()); + getChildren().setAll(children); + }); + this.loadProjectsService.setOnFailed(event -> AlertUtil.displayError("Failed to load projects", event)); + this.loadProjectsService.restart(); + } + + public void reloadOrAddProject(String projectId) { + getChildren() + .stream() + .filter(item -> item.getValue().getProjectId().equals(projectId)) + .findFirst() + .ifPresentOrElse( + item -> ((ProjectTreeItem)item).loadChildren(), + () -> getChildren().add(createProjectTreeItem(new TreeItemData(projectId)))); + } + + public void removeProject(String projectId) { + getChildren().removeIf(item -> item.getValue().getProjectId().equals(projectId)); + } + + private ProjectTreeItem createProjectTreeItem(TreeItemData info) { + var treeItem = projectTreeItemProvider.get(); + treeItem.setValue(info); + return treeItem; + } + + @Override + public boolean isLeaf() { + return false; + } +} diff --git a/src/main/java/com/erikmafo/btviewer/projectexplorer/TableItemMenuController.java b/src/main/java/com/erikmafo/btviewer/projectexplorer/TableItemMenuController.java new file mode 100644 index 0000000..3baaf6c --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/projectexplorer/TableItemMenuController.java @@ -0,0 +1,18 @@ +package com.erikmafo.btviewer.projectexplorer; + +import javafx.fxml.FXML; +import javafx.scene.control.MenuItem; + +public class TableItemMenuController { + + @FXML + private MenuItem tableSettings; + + @FXML + private MenuItem selectFirst; + + @FXML + private void initialize() { + + } +} diff --git a/src/main/java/com/erikmafo/btviewer/projectexplorer/TableTreeItem.java b/src/main/java/com/erikmafo/btviewer/projectexplorer/TableTreeItem.java new file mode 100644 index 0000000..76f0ca9 --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/projectexplorer/TableTreeItem.java @@ -0,0 +1,11 @@ +package com.erikmafo.btviewer.projectexplorer; + +import javafx.scene.control.TreeItem; + +public class TableTreeItem extends TreeItem { + + @Override + public boolean isLeaf() { + return true; + } +} diff --git a/src/main/java/com/erikmafo/btviewer/projectexplorer/TreeItemData.java b/src/main/java/com/erikmafo/btviewer/projectexplorer/TreeItemData.java new file mode 100644 index 0000000..3264fa9 --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/projectexplorer/TreeItemData.java @@ -0,0 +1,103 @@ +package com.erikmafo.btviewer.projectexplorer; + +import com.erikmafo.btviewer.model.BigtableInstance; +import com.erikmafo.btviewer.model.BigtableTable; + +import java.util.Objects; + +public class TreeItemData { + + private String projectId; + private String instanceId; + private String tableId; + + public TreeItemData() { + } + + public TreeItemData(String projectId) { + this(projectId, null, null); + } + + public TreeItemData(String projectId, String instanceId) { + this(projectId, instanceId, null); + } + + public TreeItemData(String projectId, String instanceId, String tableId) { + this.projectId = projectId; + this.instanceId = instanceId; + this.tableId = tableId; + } + + public TreeItemData(BigtableInstance instance) { + this(instance.getProjectId(), instance.getInstanceId()); + } + + public TreeItemData(BigtableTable table) { + this(table.getProjectId(), table.getInstanceId(), table.getTableId()); + } + + public boolean isRoot() { return projectId == null; } + + public boolean isProject() { + return !isRoot() && instanceId == null && tableId == null; + } + + public boolean isInstance() { + return instanceId != null && tableId == null; + } + + public boolean isTable() { + return instanceId != null && tableId != null; + } + + public String getDisplayName() { + String displayName; + if (isRoot()) { + displayName = "Projects"; + } else if (isProject()) { + displayName = projectId; + } else if (isInstance()) { + displayName = instanceId; + } else { + displayName = tableId; + } + + return displayName; + } + + public BigtableTable toTable() { + return new BigtableTable(projectId, instanceId, tableId); + } + + public BigtableInstance toInstance() { + return new BigtableInstance(projectId, instanceId); + } + + public String getProjectId() { + return projectId; + } + + public String getInstanceId() { + return instanceId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TreeItemData treeItemData = (TreeItemData) o; + + if (!projectId.equals(treeItemData.projectId)) return false; + if (!Objects.equals(instanceId, treeItemData.instanceId)) return false; + return Objects.equals(tableId, treeItemData.tableId); + } + + @Override + public int hashCode() { + int result = projectId.hashCode(); + result = 31 * result + (instanceId != null ? instanceId.hashCode() : 0); + result = 31 * result + (tableId != null ? tableId.hashCode() : 0); + return result; + } +} diff --git a/src/main/java/com/erikmafo/btviewer/services/ListTablesService.java b/src/main/java/com/erikmafo/btviewer/services/ListTablesService.java index aaa532b..de819ef 100644 --- a/src/main/java/com/erikmafo/btviewer/services/ListTablesService.java +++ b/src/main/java/com/erikmafo/btviewer/services/ListTablesService.java @@ -9,14 +9,16 @@ import javax.inject.Inject; import java.io.IOException; -import java.util.List; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.stream.Collectors; public class ListTablesService extends Service> { private final BigtableSettingsProvider settingsProvider; - - private BigtableTableAdminClient client; + private final List instances = new LinkedList<>(); private BigtableInstance instance; @Inject @@ -28,49 +30,40 @@ public void setInstance(BigtableInstance instance) { this.instance = instance; } + public void addInstances(List instances) { + for(var instance : instances) { + addInstance(instance); + } + } + + public void addInstance(BigtableInstance instance) { + instances.add(instance); + } + @Override protected Task> createTask() { return new Task<>() { @Override protected List call() throws Exception { - return getOrCreateNewClient() - .listTables() - .stream() - .map(ListTablesService.this::toBigtableTable) - .collect(Collectors.toList()); + try (var client = createClient(instance)) { + return client + .listTables() + .stream() + .map(tableId -> toBigtableTable(instance, tableId)) + .collect(Collectors.toList()); + } } }; } - private BigtableTable toBigtableTable(String tableId) { + private BigtableTable toBigtableTable(BigtableInstance instance, String tableId) { return new BigtableTable(instance.getProjectId(), instance.getInstanceId(), tableId); } - private BigtableTableAdminClient getOrCreateNewClient() throws IOException { + private BigtableTableAdminClient createClient(BigtableInstance instance) throws IOException { if (instance == null) { throw new IllegalStateException("Cannot list tables when bigtable instance is not specified"); } - - if (instance.equals(getClientInstance())) { - return client; - } - - closeClient(); - client = BigtableTableAdminClient.create(settingsProvider.getTableAdminSettings(instance)); - return client; - } - - private void closeClient() { - if (client != null) { - client.close(); - } - } - - private BigtableInstance getClientInstance() { - if (client == null) { - return null; - } - - return new BigtableInstance(client.getProjectId(), client.getInstanceId()); + return BigtableTableAdminClient.create(settingsProvider.getTableAdminSettings(instance)); } } diff --git a/src/main/java/com/erikmafo/btviewer/services/LoadInstancesService.java b/src/main/java/com/erikmafo/btviewer/services/LoadInstancesService.java index e2d20b3..8790be9 100644 --- a/src/main/java/com/erikmafo/btviewer/services/LoadInstancesService.java +++ b/src/main/java/com/erikmafo/btviewer/services/LoadInstancesService.java @@ -1,20 +1,26 @@ package com.erikmafo.btviewer.services; import com.erikmafo.btviewer.model.BigtableInstance; -import com.erikmafo.btviewer.services.internal.BigtableInstanceManager; +import com.erikmafo.btviewer.services.internal.AppDataStorage; import javafx.concurrent.Service; import javafx.concurrent.Task; import javax.inject.Inject; +import java.util.Collections; import java.util.List; public class LoadInstancesService extends Service> { - private final BigtableInstanceManager instanceManager; + private final AppDataStorage appDataStorage; + private String projectId; @Inject - public LoadInstancesService(BigtableInstanceManager instanceManager) { - this.instanceManager = instanceManager; + public LoadInstancesService(AppDataStorage appDataStorage) { + this.appDataStorage = appDataStorage; + } + + public void setProjectId(String projectId) { + this.projectId = projectId; } @Override @@ -22,7 +28,10 @@ protected Task> createTask() { return new Task<>() { @Override protected List call() throws Exception { - return instanceManager.getInstances(); + if (projectId == null) { + return Collections.emptyList(); + } + return appDataStorage.getInstances(projectId); } }; } diff --git a/src/main/java/com/erikmafo/btviewer/services/LoadProjectsService.java b/src/main/java/com/erikmafo/btviewer/services/LoadProjectsService.java new file mode 100644 index 0000000..ad74634 --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/services/LoadProjectsService.java @@ -0,0 +1,28 @@ +package com.erikmafo.btviewer.services; + +import com.erikmafo.btviewer.services.internal.AppDataStorage; +import javafx.concurrent.Service; +import javafx.concurrent.Task; + +import javax.inject.Inject; +import java.util.List; + +public class LoadProjectsService extends Service> { + + private final AppDataStorage appDataStorage; + + @Inject + public LoadProjectsService(AppDataStorage appDataStorage) { + this.appDataStorage = appDataStorage; + } + + @Override + protected Task> createTask() { + return new Task<>() { + @Override + protected List call() throws Exception { + return appDataStorage.getProjects(); + } + }; + } +} diff --git a/src/main/java/com/erikmafo/btviewer/services/LoadTableConfigurationService.java b/src/main/java/com/erikmafo/btviewer/services/LoadTableConfigurationService.java deleted file mode 100644 index b26089c..0000000 --- a/src/main/java/com/erikmafo/btviewer/services/LoadTableConfigurationService.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.erikmafo.btviewer.services; - -import com.erikmafo.btviewer.model.BigtableTable; -import com.erikmafo.btviewer.model.BigtableTableConfiguration; -import com.erikmafo.btviewer.services.internal.TableConfigManager; -import javafx.concurrent.Service; -import javafx.concurrent.Task; - -import javax.inject.Inject; - -public class LoadTableConfigurationService extends Service { - - private final TableConfigManager tableConfigManager; - private BigtableTable table; - - @Inject - public LoadTableConfigurationService(TableConfigManager tableConfigManager) { - this.tableConfigManager = tableConfigManager; - } - - public void setTable(BigtableTable table) { - this.table = table; - } - - @Override - protected Task createTask() { - return new Task<>() { - @Override - protected BigtableTableConfiguration call() throws Exception { - return tableConfigManager.getTableConfiguration(table); - } - }; - } -} diff --git a/src/main/java/com/erikmafo/btviewer/services/LoadTableSettingsService.java b/src/main/java/com/erikmafo/btviewer/services/LoadTableSettingsService.java new file mode 100644 index 0000000..981ec13 --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/services/LoadTableSettingsService.java @@ -0,0 +1,43 @@ +package com.erikmafo.btviewer.services; + +import com.erikmafo.btviewer.model.BigtableTable; +import com.erikmafo.btviewer.model.BigtableTableSettings; +import com.erikmafo.btviewer.services.internal.AppDataStorage; +import com.erikmafo.btviewer.util.AlertUtil; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.concurrent.Service; +import javafx.concurrent.Task; + +import javax.inject.Inject; + +public class LoadTableSettingsService extends Service { + + private final AppDataStorage appDataStorage; + private BigtableTable table; + + @Inject + public LoadTableSettingsService(AppDataStorage appDataStorage) { + this.appDataStorage = appDataStorage; + setOnFailed(event -> AlertUtil.displayError("Unable to load table setting", getException())); + } + + public void setTable(BigtableTable table) { + this.table = table; + } + + @Override + protected Task createTask() { + var table = this.table; + return new Task<>() { + @Override + protected BigtableTableSettings call() throws Exception { + var tableConfig = appDataStorage.getTableSettings(table); + if (tableConfig == null) { + tableConfig = new BigtableTableSettings(); + } + return tableConfig; + } + }; + } +} diff --git a/src/main/java/com/erikmafo/btviewer/services/ReadRowsService.java b/src/main/java/com/erikmafo/btviewer/services/ReadRowsService.java index bd7468e..e0e5340 100644 --- a/src/main/java/com/erikmafo/btviewer/services/ReadRowsService.java +++ b/src/main/java/com/erikmafo/btviewer/services/ReadRowsService.java @@ -1,10 +1,13 @@ package com.erikmafo.btviewer.services; import com.erikmafo.btviewer.model.*; +import com.erikmafo.btviewer.services.internal.AppDataStorage; import com.erikmafo.btviewer.services.internal.BigtableSettingsProvider; +import com.erikmafo.btviewer.sql.QueryConverter; +import com.erikmafo.btviewer.sql.SqlQuery; +import com.erikmafo.btviewer.util.AlertUtil; import com.google.cloud.bigtable.data.v2.BigtableDataClient; import com.google.cloud.bigtable.data.v2.BigtableDataSettings; -import com.google.cloud.bigtable.data.v2.models.Query; import com.google.cloud.bigtable.data.v2.models.Row; import com.google.cloud.bigtable.data.v2.models.RowCell; import javafx.concurrent.Service; @@ -12,53 +15,57 @@ import javax.inject.Inject; import java.io.IOException; -import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; import java.util.stream.Collectors; public class ReadRowsService extends Service> { private final BigtableSettingsProvider settingsProvider; + private final AppDataStorage storage; private BigtableDataClient client; private BigtableDataSettings settings; - private BigtableReadRequest readRequest; + + private BigtableInstance instance; + private SqlQuery query; @Inject - public ReadRowsService(BigtableSettingsProvider settingsProvider) { + public ReadRowsService(BigtableSettingsProvider settingsProvider, AppDataStorage storage) { this.settingsProvider = settingsProvider; + this.storage = storage; } + public void setInstance(BigtableInstance instance) { this.instance = instance; } + + public void setQuery(SqlQuery query) { this.query = query; } + @Override protected Task> createTask() { + var instance = this.instance; + var sqlQuery = this.query; + var tableId = sqlQuery.getTableName(); + return new Task<>() { @Override protected List call() throws Exception { - var rowIterator = getOrCreateNewClient() - .readRows(createQuery(readRequest)) - .iterator(); - - var bigtableRows = new ArrayList(); + var tableSettings = storage.getTableSettings(new BigtableTable(instance, tableId)); + var queryConverter = new QueryConverter(new ByteStringConverterImpl(tableSettings)); + var btQuery = queryConverter.toBigtableQuery(sqlQuery); + var rowIterator = getOrCreateNewClient(instance).readRows(btQuery).iterator(); + var bigtableRows = new LinkedList(); while (rowIterator.hasNext()) { bigtableRows.add(toBigtableRow(rowIterator.next())); - updateProgress(bigtableRows.size(), readRequest.getScan().getMaxRows()); + updateProgress(bigtableRows.size(), sqlQuery.getLimit()); + if (isCancelled()) { + break; + } } return bigtableRows; } }; } - public void setReadRequest(BigtableReadRequest readRequest) { - this.readRequest = readRequest; - } - - private static Query createQuery(BigtableReadRequest request) { - return Query - .create(request.getBigtableTable().getTableId()) - .range(request.getScan().getFrom(), request.getScan().getTo()) - .limit(request.getScan().getMaxRows()); - } - private static BigtableRow toBigtableRow(Row row) { return new BigtableRow(row.getKey().toStringUtf8(), getBigtableCells(row)); } @@ -75,18 +82,15 @@ private static BigtableCell toBigtableCell(RowCell cell) { return new BigtableCell( cell.getFamily(), cell.getQualifier().toStringUtf8(), - cell.getValue()); + cell.getValue(), + cell.getTimestamp()); } - private BigtableDataClient getOrCreateNewClient() throws IOException { - if (readRequest == null) { + private BigtableDataClient getOrCreateNewClient(BigtableInstance instance) throws IOException { + if (instance == null) { throw new IllegalStateException("Cannot list tables when read request is not specified"); } - var instance = new BigtableInstance( - readRequest.getBigtableTable().getProjectId(), - readRequest.getBigtableTable().getInstanceId()); - if (instance.equals(getClientInstance())) { return client; } diff --git a/src/main/java/com/erikmafo/btviewer/services/RemoveProjectService.java b/src/main/java/com/erikmafo/btviewer/services/RemoveProjectService.java new file mode 100644 index 0000000..c8620e5 --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/services/RemoveProjectService.java @@ -0,0 +1,35 @@ +package com.erikmafo.btviewer.services; + +import com.erikmafo.btviewer.services.internal.AppDataStorage; +import javafx.concurrent.Service; +import javafx.concurrent.Task; + +import javax.inject.Inject; + +public class RemoveProjectService extends Service { + + private final AppDataStorage storage; + private String projectId; + + @Inject + public RemoveProjectService(AppDataStorage storage) { + this.storage = storage; + } + + public void setProjectId(String projectId) { + this.projectId = projectId; + } + + @Override + protected Task createTask() { + return new Task<>() { + @Override + protected Void call() throws Exception { + if (projectId != null) { + storage.removeProject(projectId); + } + return null; + } + }; + } +} diff --git a/src/main/java/com/erikmafo/btviewer/services/SaveInstanceService.java b/src/main/java/com/erikmafo/btviewer/services/SaveInstanceService.java new file mode 100644 index 0000000..36866be --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/services/SaveInstanceService.java @@ -0,0 +1,37 @@ +package com.erikmafo.btviewer.services; + +import com.erikmafo.btviewer.model.BigtableInstance; +import com.erikmafo.btviewer.services.internal.AppDataStorage; +import javafx.concurrent.Service; +import javafx.concurrent.Task; + +import javax.inject.Inject; + +public class SaveInstanceService extends Service { + + private final AppDataStorage appDataStorage; + + private BigtableInstance instance; + + @Inject + public SaveInstanceService(AppDataStorage appDataStorage) { + this.appDataStorage = appDataStorage; + } + + public void setInstance(BigtableInstance instance) { + this.instance = instance; + } + + @Override + protected Task createTask() { + return new Task() { + @Override + protected Object call() throws Exception { + if (instance != null) { + appDataStorage.addInstance(instance); + } + return null; + } + }; + } +} diff --git a/src/main/java/com/erikmafo/btviewer/services/SaveInstancesService.java b/src/main/java/com/erikmafo/btviewer/services/SaveInstancesService.java deleted file mode 100644 index 5de9730..0000000 --- a/src/main/java/com/erikmafo/btviewer/services/SaveInstancesService.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.erikmafo.btviewer.services; - -import com.erikmafo.btviewer.model.BigtableInstance; -import com.erikmafo.btviewer.services.internal.BigtableInstanceManager; -import javafx.concurrent.Service; -import javafx.concurrent.Task; - -import javax.inject.Inject; -import java.util.ArrayList; -import java.util.List; - -public class SaveInstancesService extends Service { - - private final BigtableInstanceManager instanceManager; - - private List instances; - - @Inject - public SaveInstancesService(BigtableInstanceManager instanceManager) { - this.instanceManager = instanceManager; - } - - public void addInstance(BigtableInstance instance) { - - if (this.instances == null) { - instances = new ArrayList<>(); - } - - if (!this.instances.contains(instance)) { - this.instances.add(instance); - } - } - - @Override - protected Task createTask() { - return new Task() { - @Override - protected Object call() throws Exception { - instanceManager.setInstances(instances); - return null; - } - }; - } -} diff --git a/src/main/java/com/erikmafo/btviewer/services/SaveTableConfigurationService.java b/src/main/java/com/erikmafo/btviewer/services/SaveTableSettingsService.java similarity index 51% rename from src/main/java/com/erikmafo/btviewer/services/SaveTableConfigurationService.java rename to src/main/java/com/erikmafo/btviewer/services/SaveTableSettingsService.java index f2df8ce..016b0ae 100644 --- a/src/main/java/com/erikmafo/btviewer/services/SaveTableConfigurationService.java +++ b/src/main/java/com/erikmafo/btviewer/services/SaveTableSettingsService.java @@ -1,26 +1,26 @@ package com.erikmafo.btviewer.services; import com.erikmafo.btviewer.model.BigtableTable; -import com.erikmafo.btviewer.model.BigtableTableConfiguration; -import com.erikmafo.btviewer.services.internal.TableConfigManager; +import com.erikmafo.btviewer.model.BigtableTableSettings; +import com.erikmafo.btviewer.services.internal.AppDataStorage; import javafx.concurrent.Service; import javafx.concurrent.Task; import javax.inject.Inject; -public class SaveTableConfigurationService extends Service { +public class SaveTableSettingsService extends Service { - private final TableConfigManager tableConfigManager; + private final AppDataStorage appDataStorage; private BigtableTable table; - private BigtableTableConfiguration tableConfiguration; + private BigtableTableSettings tableConfiguration; @Inject - public SaveTableConfigurationService(TableConfigManager tableConfigManager) { - this.tableConfigManager = tableConfigManager; + public SaveTableSettingsService(AppDataStorage appDataStorage) { + this.appDataStorage = appDataStorage; } - public void setTableConfiguration(BigtableTable table, BigtableTableConfiguration tableConfiguration) { + public void setTableConfiguration(BigtableTable table, BigtableTableSettings tableConfiguration) { this.table = table; this.tableConfiguration = tableConfiguration; } @@ -30,7 +30,7 @@ protected Task createTask() { return new Task() { @Override protected Object call() throws Exception { - tableConfigManager.saveTableConfiguration(table, tableConfiguration); + appDataStorage.saveTableConfiguration(table, tableConfiguration); return null; } }; diff --git a/src/main/java/com/erikmafo/btviewer/services/ServicesModule.java b/src/main/java/com/erikmafo/btviewer/services/ServicesModule.java index 9c8905f..f804f5a 100644 --- a/src/main/java/com/erikmafo/btviewer/services/ServicesModule.java +++ b/src/main/java/com/erikmafo/btviewer/services/ServicesModule.java @@ -2,47 +2,43 @@ import com.erikmafo.btviewer.config.AppConfig; import com.erikmafo.btviewer.config.ApplicationEnvironment; -import com.erikmafo.btviewer.services.internal.inmemory.BigtableEmulatorSettingsProvider; -import com.erikmafo.btviewer.services.internal.inmemory.InMemoryInstanceManager; -import com.erikmafo.btviewer.services.internal.inmemory.InMemoryTableConfigManager; -import com.erikmafo.btviewer.services.internal.inmemory.TestDataUtil; +import com.erikmafo.btviewer.services.internal.BigtableEmulatorSettingsProvider; +import com.erikmafo.btviewer.services.internal.TestDataUtil; import com.erikmafo.btviewer.services.internal.*; import com.google.api.gax.core.CredentialsProvider; -import com.google.inject.Binder; -import com.google.inject.Module; +import com.google.inject.*; -public class ServicesModule implements Module { +public class ServicesModule extends AbstractModule { - @Override - public void configure(Binder binder) { - var config = AppConfig.load(ApplicationEnvironment.get()); + private final AppConfig config; + + public ServicesModule() { + this(AppConfig.load(ApplicationEnvironment.get())); + } + + public ServicesModule(AppConfig config) { + this.config = config; + } + @Override + protected void configure() { if (config.useBigtableEmulator()) { var emulatorSettingsProvider = new BigtableEmulatorSettingsProvider(); emulatorSettingsProvider.startEmulator(); TestDataUtil.injectWithTestData(emulatorSettingsProvider); - binder.bind(BigtableSettingsProvider.class).toInstance(emulatorSettingsProvider); + bind(BigtableSettingsProvider.class).toInstance(emulatorSettingsProvider); } else { - binder.bind(BigtableSettingsProvider.class).to(BigtableSettingsProviderImpl.class); + bind(BigtableSettingsProvider.class).to(BigtableSettingsProviderImpl.class); } - binder.bind(CredentialsProvider.class).to(DynamicCredentialsProvider.class); + bind(CredentialsProvider.class).to(DynamicCredentialsProvider.class); - if (config.useInMemoryTableConfigManager()) { - var inMemoryTableConfigManager = new InMemoryTableConfigManager(); - binder.bind(TableConfigManager.class).toInstance(inMemoryTableConfigManager); + if (config.useInMemoryDatabase()) { + bind(AppDataStorage.class).toInstance(AppDataStorageImpl.createInMemory()); } else { - binder.bind(TableConfigManager.class).toInstance(new TableConfigManagerImpl()); - } - - if (config.useInMemoryInstanceManager()) { - var inMemoryInstanceManager = new InMemoryInstanceManager(); - TestDataUtil.injectWithTestData(inMemoryInstanceManager); - binder.bind(BigtableInstanceManager.class).toInstance(inMemoryInstanceManager); - } else { - binder.bind(BigtableInstanceManager.class).toInstance(new BigtableInstanceManagerImpl()); + bind(AppDataStorage.class).toInstance(AppDataStorageImpl.createInstance()); } } } diff --git a/src/main/java/com/erikmafo/btviewer/services/internal/AppDataStorage.java b/src/main/java/com/erikmafo/btviewer/services/internal/AppDataStorage.java new file mode 100644 index 0000000..6064dd4 --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/services/internal/AppDataStorage.java @@ -0,0 +1,26 @@ +package com.erikmafo.btviewer.services.internal; + +import com.erikmafo.btviewer.model.BigtableInstance; +import com.erikmafo.btviewer.model.BigtableTable; +import com.erikmafo.btviewer.model.BigtableTableSettings; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +public interface AppDataStorage { + + List getProjects() throws IOException; + + void removeProject(String projectId) throws IOException; + + List getInstances(String projectId) throws IOException; + + void addInstance(BigtableInstance instance) throws IOException; + + void removeInstance(BigtableInstance instance) throws IOException; + + BigtableTableSettings getTableSettings(BigtableTable table) throws IOException; + + void saveTableConfiguration(BigtableTable table, BigtableTableSettings configuration) throws IOException; +} diff --git a/src/main/java/com/erikmafo/btviewer/services/internal/AppDataStorageImpl.java b/src/main/java/com/erikmafo/btviewer/services/internal/AppDataStorageImpl.java new file mode 100644 index 0000000..9894a25 --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/services/internal/AppDataStorageImpl.java @@ -0,0 +1,164 @@ +package com.erikmafo.btviewer.services.internal; + +import com.erikmafo.btviewer.model.BigtableInstance; +import com.erikmafo.btviewer.model.BigtableTable; +import com.erikmafo.btviewer.model.BigtableTableSettings; +import com.google.gson.Gson; +import com.google.inject.Singleton; +import org.jetbrains.annotations.NotNull; +import org.mapdb.*; + +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentMap; +import java.util.stream.Collectors; + +@Singleton +public class AppDataStorageImpl implements AppDataStorage { + + public static final String INSTANCES = "instances"; + public static final String TABLE_SETTINGS = "table-settings"; + + public static AppDataStorage createInMemory() { + var database = DBMaker + .memoryDB() + .transactionEnable() + .closeOnJvmShutdown() + .make(); + return new AppDataStorageImpl(database); + } + + public static AppDataStorage createInstance() { + var path = AppDataUtil + .getStorageFolder() + .resolve("database"); + var database = DBMaker + .fileDB(path.toFile()) + .transactionEnable() + .closeOnJvmShutdown() + .make(); + return new AppDataStorageImpl(database); + } + + private static class JsonSerializer implements Serializer { + private final Class clazz; + private final Gson gson = new Gson(); + + public JsonSerializer(Class clazz) { + this.clazz = clazz; + } + + @Override + public void serialize(@NotNull DataOutput2 output, @NotNull T t) throws IOException { + output.writeUTF(gson.toJson(t)); + } + + @Override + public T deserialize(@NotNull DataInput2 input, int i) throws IOException { + return gson.fromJson(input.readUTF(), clazz); + } + } + + private final DB database; + private final ConcurrentMap instances; + private final ConcurrentMap tableSettings; + + public AppDataStorageImpl(DB database) { + this.database = database; + this.instances = openOrCreateInstances(); + this.tableSettings = openOrCreateTableSettings(); + } + + @Override + public List getProjects() { + return openOrCreateInstances() + .values() + .stream() + .map(BigtableInstance::getProjectId) + .distinct() + .collect(Collectors.toList()); + } + + @Override + public void removeProject(String projectId) { + for (var instance : getInstances(projectId)) { + removeInstanceNoCommit(instance); + } + database.commit(); + } + + @Override + public List getInstances(String projectId) { + return openOrCreateInstances() + .values() + .stream() + .filter(instance -> instance.getProjectId().equals(projectId)) + .collect(Collectors.toList()); + } + + @Override + public void addInstance(BigtableInstance instance) { + openOrCreateInstances().put(getKey(instance), instance); + database.commit(); + } + + @Override + public void removeInstance(BigtableInstance instance) { + removeInstanceNoCommit(instance); + database.commit(); + } + + @Override + public BigtableTableSettings getTableSettings(BigtableTable table) { + return this.openOrCreateTableSettings().getOrDefault(getKey(table), new BigtableTableSettings()); + } + + @Override + public void saveTableConfiguration(BigtableTable table, BigtableTableSettings settings) { + this.openOrCreateTableSettings().put(getKey(table), settings); + this.database.commit(); + } + + @NotNull + private String getKey(BigtableInstance instance) { + return String.format("projects/%s/instances/%s", instance.getProjectId(), instance.getInstanceId()); + } + + @NotNull + private String getKey(BigtableTable table) { + return String.format("projects/%s/instances/%s/tables/%s", table.getProjectId(), table.getInstanceId(), table.getTableId()); + } + + @NotNull + private ConcurrentMap openOrCreateTableSettings() { + return this.database + .hashMap(TABLE_SETTINGS) + .keySerializer(Serializer.STRING) + .valueSerializer(new JsonSerializer<>(BigtableTableSettings.class)) + .createOrOpen(); + } + + @NotNull + private ConcurrentMap openOrCreateInstances() { + return this.database + .hashMap(INSTANCES) + .keySerializer(Serializer.STRING) + .valueSerializer(new JsonSerializer<>(BigtableInstance.class)) + .createOrOpen(); + } + + private void removeInstanceNoCommit(BigtableInstance instance) { + var instanceKey = getKey(instance); + instances.remove(instanceKey); + var tablesToRemove = tableSettings.keySet() + .stream() + .filter(key -> key.startsWith(String.format("%s/tables/", instanceKey))) + .collect(Collectors.toList()); + for (var tableKey : tablesToRemove) { + tableSettings.remove(tableKey); + } + } +} + + + diff --git a/src/main/java/com/erikmafo/btviewer/services/internal/inmemory/BigtableEmulatorSettingsProvider.java b/src/main/java/com/erikmafo/btviewer/services/internal/BigtableEmulatorSettingsProvider.java similarity index 96% rename from src/main/java/com/erikmafo/btviewer/services/internal/inmemory/BigtableEmulatorSettingsProvider.java rename to src/main/java/com/erikmafo/btviewer/services/internal/BigtableEmulatorSettingsProvider.java index 0860ba9..f3cebb9 100644 --- a/src/main/java/com/erikmafo/btviewer/services/internal/inmemory/BigtableEmulatorSettingsProvider.java +++ b/src/main/java/com/erikmafo/btviewer/services/internal/BigtableEmulatorSettingsProvider.java @@ -1,4 +1,4 @@ -package com.erikmafo.btviewer.services.internal.inmemory; +package com.erikmafo.btviewer.services.internal; import com.erikmafo.btviewer.model.BigtableInstance; import com.erikmafo.btviewer.services.internal.BigtableSettingsProvider; diff --git a/src/main/java/com/erikmafo/btviewer/services/internal/BigtableInstanceManager.java b/src/main/java/com/erikmafo/btviewer/services/internal/BigtableInstanceManager.java deleted file mode 100644 index 5a4e833..0000000 --- a/src/main/java/com/erikmafo/btviewer/services/internal/BigtableInstanceManager.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.erikmafo.btviewer.services.internal; - -import com.erikmafo.btviewer.model.BigtableInstance; - -import java.io.IOException; -import java.util.List; - -public interface BigtableInstanceManager { - List getInstances() throws IOException; - - void setInstances(List instances) throws IOException; -} diff --git a/src/main/java/com/erikmafo/btviewer/services/internal/BigtableInstanceManagerImpl.java b/src/main/java/com/erikmafo/btviewer/services/internal/BigtableInstanceManagerImpl.java deleted file mode 100644 index a2a776a..0000000 --- a/src/main/java/com/erikmafo/btviewer/services/internal/BigtableInstanceManagerImpl.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.erikmafo.btviewer.services.internal; - -import com.erikmafo.btviewer.model.BigtableInstance; -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; -import java.io.IOException; -import java.lang.reflect.Type; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; - -public class BigtableInstanceManagerImpl implements BigtableInstanceManager { - - private static final Type LIST_TYPE = new TypeToken>() {}.getType(); - private static final String INSTANCES = "instances"; - private final Object mutex = new Object(); - private final Gson gson = new Gson(); - private List bigtableInstances; - - public BigtableInstanceManagerImpl() { - - } - - @Override - public List getInstances() throws IOException { - synchronized (mutex) { - - if (bigtableInstances != null) { - return bigtableInstances; - } - - String json = null; - - if (Files.exists(getConfigurationFile())) - { - json = Files.readString(getConfigurationFile()); - } - - if (json != null) { - bigtableInstances = gson.fromJson(json, LIST_TYPE); - } else { - bigtableInstances = new ArrayList<>(); - } - - return bigtableInstances; - } - } - - @Override - public void setInstances(List instances) throws IOException { - - synchronized (mutex) { - this.bigtableInstances = instances; - var file = getConfigurationFile(); - if (!Files.exists(file)) { - Files.createDirectories(file.getParent()); - } - String json = gson.toJson(instances, LIST_TYPE); - Files.writeString(getConfigurationFile(), json); - } - } - - private Path getConfigurationFile() { - return AppDataUtil - .getStorageFolder() - .resolve(INSTANCES); - } -} - diff --git a/src/main/java/com/erikmafo/btviewer/services/internal/TableConfigManager.java b/src/main/java/com/erikmafo/btviewer/services/internal/TableConfigManager.java deleted file mode 100644 index e66cb97..0000000 --- a/src/main/java/com/erikmafo/btviewer/services/internal/TableConfigManager.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.erikmafo.btviewer.services.internal; - -import com.erikmafo.btviewer.model.BigtableTable; -import com.erikmafo.btviewer.model.BigtableTableConfiguration; - -import java.io.IOException; -import java.nio.file.Path; - -public interface TableConfigManager { - BigtableTableConfiguration getTableConfiguration(BigtableTable table) throws IOException; - - void saveTableConfiguration(BigtableTable table, BigtableTableConfiguration configuration) throws IOException; -} diff --git a/src/main/java/com/erikmafo/btviewer/services/internal/TableConfigManagerImpl.java b/src/main/java/com/erikmafo/btviewer/services/internal/TableConfigManagerImpl.java deleted file mode 100644 index 76715d4..0000000 --- a/src/main/java/com/erikmafo/btviewer/services/internal/TableConfigManagerImpl.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.erikmafo.btviewer.services.internal; - -import com.erikmafo.btviewer.model.BigtableTable; -import com.erikmafo.btviewer.model.BigtableTableConfiguration; -import com.google.gson.Gson; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -public class TableConfigManagerImpl implements TableConfigManager { - - private static final String TABLES = "table-configs"; - - private static Gson gson = new Gson(); - - @Override - public BigtableTableConfiguration getTableConfiguration(BigtableTable table) throws IOException { - String json = null; - var tableConfigFile = getConfigurationFile(table); - if (Files.exists(tableConfigFile)) - { - json = Files.readString(tableConfigFile); - } - - if (json == null) { - return null; - } - - return gson.fromJson(json, BigtableTableConfiguration.class); - } - - @Override - public void saveTableConfiguration(BigtableTable table, BigtableTableConfiguration configuration) throws IOException { - var configFile = getConfigurationFile(table); - createDirectoriesIfNeeded(configFile); - Files.writeString(getConfigurationFile(table), gson.toJson(configuration)); - } - - private void createDirectoriesIfNeeded(Path tableConfigFile) throws IOException { - if (!Files.exists(tableConfigFile.getParent())) { - Files.createDirectories(tableConfigFile.getParent()); - } - } - - private Path getConfigurationFile(BigtableTable table) { - return AppDataUtil - .getStorageFolder() - .resolve(TableConfigManagerImpl.TABLES) - .resolve(table.getProjectId()) - .resolve(table.getInstanceId()) - .resolve(table.getTableId() + ".json"); - } - -} diff --git a/src/main/java/com/erikmafo/btviewer/services/internal/TestDataUtil.java b/src/main/java/com/erikmafo/btviewer/services/internal/TestDataUtil.java new file mode 100644 index 0000000..cd3b52c --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/services/internal/TestDataUtil.java @@ -0,0 +1,83 @@ +package com.erikmafo.btviewer.services.internal; + +import com.erikmafo.btviewer.model.*; +import com.google.cloud.bigtable.admin.v2.BigtableTableAdminClient; +import com.google.cloud.bigtable.admin.v2.models.CreateTableRequest; +import com.google.cloud.bigtable.data.v2.BigtableDataClient; +import com.google.cloud.bigtable.data.v2.models.RowMutation; +import com.google.protobuf.ByteString; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import static com.erikmafo.btviewer.util.ByteStringConverterUtil.toByteString; + +public class TestDataUtil { + + private static final String PROJECT_0 = "project-0"; + private static final String INSTANCE_0 = "instance-0"; + private static final String INSTANCE_1 = "instance-1"; + private static final String TABLE_0 = "table-0"; + private static final String TABLE_1 = "table-1"; + + public static void injectWithTestData(BigtableEmulatorSettingsProvider settingsProvider) { + try { + createTableWithTestData(settingsProvider, new BigtableInstance(PROJECT_0, INSTANCE_0), TABLE_0); + // createTableWithTestData(settingsProvider, new BigtableInstance(PROJECT_0, INSTANCE_0), TABLE_1); + // createTableWithTestData(settingsProvider, new BigtableInstance(PROJECT_0, INSTANCE_1), TABLE_0); + } catch (IOException e) { + throw new RuntimeException("Unable to start emulator", e); + } + } + + private static void createTableWithTestData(BigtableEmulatorSettingsProvider settingsProvider, BigtableInstance instance, String tableName) throws IOException { + var tableAdminSettings = settingsProvider.getTableAdminSettings(instance); + try(var adminClient = BigtableTableAdminClient.create(tableAdminSettings)) { + adminClient.createTable(CreateTableRequest.of(tableName) + .addFamily("f1") + .addFamily("f2") + .addFamily("f3")); + var dataSettings = settingsProvider.getDataSettings(instance); + try(var dataClient = BigtableDataClient.create(dataSettings)) { + for (int i = 0; i < 1000; i++) { + var rowKey = "row-" + String.format("%04d", i); + var mutation = RowMutation + .create(tableName, rowKey) + .setCell("f1", "q1", "string-" + i) + .setCell("f1", toByteString("q2"), toByteString(i)) + .setCell("f1", toByteString("q3"), toByteString(i + 0.5)) + .setCell("f2", toByteString("q4"), toByteString("string-" + i)) + .setCell("f3", toByteString("q5"), toByteString("string-" + i)); + dataClient.mutateRow(mutation); + } + } + } + } + + /*private static ByteString toByteString(String value) { + return ByteString.copyFromUtf8(value); + } + + private static ByteString toByteString(int value) { + return ByteString.copyFrom(toByteArray(value)); + } + + private static ByteString toByteString(double value) { + return ByteString.copyFrom(toByteArray(value)); + } + + private static byte[] toByteArray(int i) { + final ByteBuffer bb = ByteBuffer.allocate(Integer.SIZE / Byte.SIZE); + bb.order(ByteOrder.BIG_ENDIAN); + bb.putInt(i); + return bb.array(); + } + + private static byte[] toByteArray(double i) { + final ByteBuffer bb = ByteBuffer.allocate(Double.SIZE / Byte.SIZE); + bb.order(ByteOrder.BIG_ENDIAN); + bb.putDouble(i); + return bb.array(); + }*/ +} diff --git a/src/main/java/com/erikmafo/btviewer/services/internal/inmemory/InMemoryInstanceManager.java b/src/main/java/com/erikmafo/btviewer/services/internal/inmemory/InMemoryInstanceManager.java deleted file mode 100644 index 0eda788..0000000 --- a/src/main/java/com/erikmafo/btviewer/services/internal/inmemory/InMemoryInstanceManager.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.erikmafo.btviewer.services.internal.inmemory; - -import com.erikmafo.btviewer.model.BigtableInstance; -import com.erikmafo.btviewer.services.internal.BigtableInstanceManager; - -import java.util.ArrayList; -import java.util.List; - -public class InMemoryInstanceManager implements BigtableInstanceManager { - - private final Object mutex = new Object(); - private List instances; - - @Override - public List getInstances() { - synchronized (mutex) { - return new ArrayList<>(instances); - } - } - - @Override - public void setInstances(List instances) { - synchronized (mutex) { - this.instances = new ArrayList<>(); - this.instances.addAll(instances); - } - } -} diff --git a/src/main/java/com/erikmafo/btviewer/services/internal/inmemory/InMemoryTableConfigManager.java b/src/main/java/com/erikmafo/btviewer/services/internal/inmemory/InMemoryTableConfigManager.java deleted file mode 100644 index 8abbe96..0000000 --- a/src/main/java/com/erikmafo/btviewer/services/internal/inmemory/InMemoryTableConfigManager.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.erikmafo.btviewer.services.internal.inmemory; - -import com.erikmafo.btviewer.model.BigtableTable; -import com.erikmafo.btviewer.model.BigtableTableConfiguration; -import com.erikmafo.btviewer.services.internal.TableConfigManager; -import java.util.HashMap; -import java.util.Map; - -public class InMemoryTableConfigManager implements TableConfigManager { - - private final Map configs = new HashMap<>(); - - @Override - public BigtableTableConfiguration getTableConfiguration(BigtableTable table) { - return configs.getOrDefault(table, null); - } - - @Override - public void saveTableConfiguration(BigtableTable table, BigtableTableConfiguration configuration) { - configs.put(table, configuration); - } -} diff --git a/src/main/java/com/erikmafo/btviewer/services/internal/inmemory/TestDataUtil.java b/src/main/java/com/erikmafo/btviewer/services/internal/inmemory/TestDataUtil.java deleted file mode 100644 index d18a291..0000000 --- a/src/main/java/com/erikmafo/btviewer/services/internal/inmemory/TestDataUtil.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.erikmafo.btviewer.services.internal.inmemory; - -import com.erikmafo.btviewer.model.*; -import com.google.cloud.bigtable.admin.v2.BigtableTableAdminClient; -import com.google.cloud.bigtable.admin.v2.models.CreateTableRequest; -import com.google.cloud.bigtable.data.v2.BigtableDataClient; -import com.google.cloud.bigtable.data.v2.models.RowMutation; -import com.google.protobuf.ByteString; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -public class TestDataUtil { - - private static final String PROJECT_0 = "project-0"; - private static final String INSTANCE_0 = "instance-0"; - private static final String TABLE_0 = "table-0"; - - public static void injectWithTestData(BigtableEmulatorSettingsProvider settingsProvider) { - try { - var instance = new BigtableInstance(PROJECT_0, INSTANCE_0); - var tableAdminSettings = settingsProvider.getTableAdminSettings(instance); - var adminClient = BigtableTableAdminClient.create(tableAdminSettings); - adminClient.createTable(CreateTableRequest - .of(TABLE_0) - .addFamily("f1") - .addFamily("f2") - .addFamily("f3")); - adminClient.close(); - - var dataSettings = settingsProvider.getDataSettings(instance); - var dataClient = BigtableDataClient.create(dataSettings); - injectWithTestData(dataClient); - dataClient.close(); - } catch (IOException e) { - throw new RuntimeException("Unable to start emulator", e); - } - } - - public static void injectWithTestData(InMemoryInstanceManager instanceManager) { - var instance = new BigtableInstance(PROJECT_0, INSTANCE_0); - instanceManager.setInstances(Arrays.asList(instance)); - } - - public static void injectWithTestData(InMemoryTableConfigManager configManager) { - var table = new BigtableTable(PROJECT_0, INSTANCE_0, TABLE_0); - var config = new BigtableTableConfiguration(); - configManager.saveTableConfiguration(table, config); - } - - private static void injectWithTestData(BigtableDataClient client) { - for (int i = 0; i <1000; i++) { - var rowKey = "row-" + String.format("%04d", i); - var mutation = RowMutation - .create(TABLE_0, rowKey) - .setCell("f1", "q1", "string-" + i) - .setCell("f1", toByteString("q2"), toByteString(i)) - .setCell("f1", toByteString("q3"), toByteString(i + 0.5)); - client.mutateRow(mutation); - } - } - - private static ByteString toByteString(String value) { - return ByteString.copyFromUtf8(value); - } - - private static ByteString toByteString(int value) { - return ByteString.copyFrom(toByteArray(value)); - } - - private static ByteString toByteString(double value) { - return ByteString.copyFrom(toByteArray(value)); - } - - private static byte[] toByteArray(int i) { - final ByteBuffer bb = ByteBuffer.allocate(Integer.SIZE / Byte.SIZE); - bb.order(ByteOrder.BIG_ENDIAN); - bb.putInt(i); - return bb.array(); - } - - private static byte[] toByteArray(double i) { - final ByteBuffer bb = ByteBuffer.allocate(Double.SIZE / Byte.SIZE); - bb.order(ByteOrder.BIG_ENDIAN); - bb.putDouble(i); - return bb.array(); - } -} diff --git a/src/main/java/com/erikmafo/btviewer/sql/ByteStringConverter.java b/src/main/java/com/erikmafo/btviewer/sql/ByteStringConverter.java new file mode 100644 index 0000000..1dda309 --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/sql/ByteStringConverter.java @@ -0,0 +1,7 @@ +package com.erikmafo.btviewer.sql; + +import com.google.protobuf.ByteString; + +public interface ByteStringConverter { + ByteString toByteString(Field field, Value value); +} diff --git a/src/main/java/com/erikmafo/btviewer/sql/DateTimeFormatUtil.java b/src/main/java/com/erikmafo/btviewer/sql/DateTimeFormatUtil.java new file mode 100644 index 0000000..0b6b90c --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/sql/DateTimeFormatUtil.java @@ -0,0 +1,65 @@ +package com.erikmafo.btviewer.sql; + +import java.time.*; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class DateTimeFormatUtil { + + private static final String DATE = "uuuu-MM-dd"; + private static final String DATE_HOUR = "uuuu-MM-dd HH"; + private static final String DATE_HOUR_MINUTE = "uuuu-MM-dd HH:mm"; + private static final String DATE_HOUR_MINUTE_SECOND = "uuuu-MM-dd HH:mm:ss"; + private static final String DATE_HOUR_MINUTE_SECOND_MILLIS = "uuuu-MM-dd HH:mm:ss:SS"; + + public static long toMicros(String dateTime) { + return TimeUnit.MILLISECONDS.toMicros(toEpochMilli(dateTime)); + } + + private static long toEpochMilli(String dateTime) { + String format; + var isDate = false; + if (dateTime.length() == DATE.length()) { + isDate = true; + format = DATE; + } else if (dateTime.length() == DATE_HOUR.length()) { + format = DATE_HOUR; + } else if (dateTime.length() == DATE_HOUR_MINUTE.length()) { + format = DATE_HOUR_MINUTE; + } else if (dateTime.length() == DATE_HOUR_MINUTE_SECOND.length()) { + format = DATE_HOUR_MINUTE_SECOND; + } else if (dateTime.length() == DATE_HOUR_MINUTE_SECOND_MILLIS.length()) { + format = DATE_HOUR_MINUTE_SECOND_MILLIS; + } else { + throw new IllegalArgumentException(getErrorMessage(dateTime)); + } + return toMillis(dateTime, format, isDate); + } + + private static long toMillis(String dateTime, String format, boolean isDate) { + dateTime = dateTime.trim(); + var offset = ZoneOffset.ofHours(0); + var formatter = DateTimeFormatter.ofPattern(format); + var dt = isDate ? + LocalDateTime.of(LocalDate.parse(dateTime, formatter), LocalTime.of(0, 0, 0)) : + LocalDateTime.parse(dateTime, formatter); + return dt.toInstant(offset).toEpochMilli(); + } + + private static String getErrorMessage(String dateTime) { + return String.format("Could not parse %s as date. Supported formats are: \n%s", + dateTime, String.join("\n", supportedFormats())); + } + + private static List supportedFormats() { + return Arrays.asList( + DATE, + DATE_HOUR, + DATE_HOUR_MINUTE, + DATE_HOUR_MINUTE_SECOND, + DATE_HOUR_MINUTE_SECOND_MILLIS + ); + } +} diff --git a/src/main/java/com/erikmafo/btviewer/sql/Field.java b/src/main/java/com/erikmafo/btviewer/sql/Field.java new file mode 100644 index 0000000..31a7c55 --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/sql/Field.java @@ -0,0 +1,50 @@ +package com.erikmafo.btviewer.sql; + + +public class Field { + + private final String name; + + public Field(String name) { + + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException("'name' cannot be null or empty"); + } + + this.name = name; + } + + public String getName() { + return name; + } + + public boolean isRowKey() { + return name.toUpperCase().equals("KEY"); + } + + public boolean isTimestamp() { return name.toUpperCase().equals("TIMESTAMP"); } + + public boolean isAsterisk() { + return name.equals("*"); + } + + public String getFamily() { + return getParts()[0]; + } + + public boolean hasQualifier() { + return getParts().length > 1; + } + + public String getQualifier() { + var parts = getParts(); + if (parts.length > 1) { + return parts[1]; + } + return null; + } + + private String[] getParts() { + return name.split("\\."); + } +} diff --git a/src/main/java/com/erikmafo/btviewer/sql/Operator.java b/src/main/java/com/erikmafo/btviewer/sql/Operator.java new file mode 100644 index 0000000..2679441 --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/sql/Operator.java @@ -0,0 +1,26 @@ +package com.erikmafo.btviewer.sql; + +import java.util.Arrays; + +public enum Operator { + EQUAL("="), + LESS_THAN("<"), + LESS_THAN_OR_EQUAL("<="), + GREATER_THAN(">"), + GREATER_THAN_OR_EQUAL(">="), + LIKE("LIKE"); + + private final String value; + + Operator(String value) { + this.value = value; + } + + static Operator of(String value) { + return Arrays + .stream(values()) + .filter(operator -> operator.value.equals(value)) + .findFirst() + .orElseThrow(); + } +} diff --git a/src/main/java/com/erikmafo/btviewer/sql/QueryConverter.java b/src/main/java/com/erikmafo/btviewer/sql/QueryConverter.java new file mode 100644 index 0000000..620a80c --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/sql/QueryConverter.java @@ -0,0 +1,243 @@ +package com.erikmafo.btviewer.sql; + +import com.google.cloud.bigtable.data.v2.models.Filters; +import com.google.cloud.bigtable.data.v2.models.Query; +import com.google.cloud.bigtable.data.v2.models.Range; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +import static com.google.cloud.bigtable.data.v2.models.Filters.FILTERS; + +public class QueryConverter { + private final ByteStringConverter byteStringConverter; + + public QueryConverter(ByteStringConverter byteStringConverter) { + this.byteStringConverter = byteStringConverter; + } + + public Query toBigtableQuery(SqlQuery sqlQuery) { + var bigtableQuery = Query.create(sqlQuery.getTableName()); + setRowRange(bigtableQuery, sqlQuery); + bigtableQuery.filter(getFilter(sqlQuery)); + bigtableQuery.limit(sqlQuery.getLimit()); + return bigtableQuery; + } + + private void setRowRange(Query bigtableQuery, SqlQuery sqlQuery) { + WhereClause rowKeyEq = null; + var rowRange = Range.ByteStringRange.unbounded(); + for (var whereClause : filterRowKeyWhereClauses(sqlQuery)) { + switch (whereClause.getOperator()) { + case EQUAL: + rowKeyEq = whereClause; + break; + case LESS_THAN: + rowRange.endClosed(whereClause.getValue().asString()); + break; + case LESS_THAN_OR_EQUAL: + rowRange.endOpen(whereClause.getValue().asString()); + break; + case GREATER_THAN: + rowRange.startOpen(whereClause.getValue().asString()); + break; + case GREATER_THAN_OR_EQUAL: + rowRange.startClosed(whereClause.getValue().asString()); + break; + default: + break; + } + } + + if (rowKeyEq != null) { + bigtableQuery.rowKey(rowKeyEq.getValue().asString()); + } else { + bigtableQuery.range(rowRange); + } + } + + private List filterRowKeyWhereClauses(SqlQuery sqlQuery) { + return sqlQuery.getWhereClauses() + .stream() + .filter(where -> where.getField().isRowKey()) + .collect(Collectors.toList()); + } + + private List filterTimestampWhereClauses(SqlQuery sqlQuery) { + return sqlQuery.getWhereClauses() + .stream() + .filter(where -> where.getField().isTimestamp()) + .collect(Collectors.toList()); + } + + private Filters.Filter getFilter(SqlQuery sqlQuery) { + return chain(Arrays.asList( + getRowKeyRegexFilter(sqlQuery), + getTimestampFilter(sqlQuery), + getValueFilter(sqlQuery), + getFamilyQualifierFilter(sqlQuery))); + } + + private Filters.Filter getRowKeyRegexFilter(SqlQuery sqlQuery) { + var filters = sqlQuery.getWhereClauses().stream() + .filter(w -> w.getField().isRowKey()) + .filter(w -> w.getOperator().equals(Operator.LIKE)) + .map(w -> FILTERS.key().regex(w.getValue().asString())) + .collect(Collectors.toList()); + return chain(filters); + } + + private Filters.Filter getValueFilter(SqlQuery sqlQuery) { + var filters = sqlQuery.getWhereClauses().stream() + .filter(w -> !w.getField().isTimestamp()) + .filter(w -> !w.getField().isRowKey()) + .map(this::getValueFilter) + .collect(Collectors.toList()); + return chain(filters); + } + + private Filters.Filter getValueFilter(WhereClause where) { + var condition = FILTERS.chain() + .filter(FILTERS.chain() + .filter(getFamilyQualifierFilter(where.getField())) + .filter(getValueFilterCore(where)) + .filter(FILTERS.limit().cellsPerColumn(1))) + .filter(FILTERS.offset().cellsPerRow(1)); + return FILTERS + .condition(condition) + .then(FILTERS.pass()) + .otherwise(FILTERS.block()); + } + + private Filters.Filter getValueFilterCore(WhereClause where) { + var valueAsByteString = byteStringConverter.toByteString(where.getField(), where.getValue()); + switch (where.getOperator()) { + case EQUAL: + return FILTERS.value().exactMatch(valueAsByteString); + case LESS_THAN: + return FILTERS.value().range().endOpen(valueAsByteString); + case LESS_THAN_OR_EQUAL: + return FILTERS.value().range().endClosed(valueAsByteString); + case GREATER_THAN: + return FILTERS.value().range().startOpen(valueAsByteString); + case GREATER_THAN_OR_EQUAL: + return FILTERS.value().range().startClosed(valueAsByteString); + case LIKE: + return FILTERS.value().regex(where.getValue().asString()); + default: + throw new IllegalArgumentException( + String.format("operator %s is not supported for filtering values", where.getOperator())); + } + } + + private Filters.Filter getFamilyQualifierFilter(SqlQuery sqlQuery) { + var filters = new LinkedList(); + filters.addAll(getFamilyFilters(sqlQuery)); + filters.addAll(getQualifierFilters(sqlQuery)); + return interleave(filters); + } + + private List getFamilyFilters(SqlQuery sqlQuery) { + return sqlQuery.getFields().stream() + .filter(f -> !f.isAsterisk()) + .filter(f -> !f.hasQualifier()) + .map(f -> FILTERS.family().exactMatch(f.getFamily())) + .collect(Collectors.toList()); + } + + private List getQualifierFilters(SqlQuery sqlQuery) { + return sqlQuery.getFields() + .stream() + .filter(f -> !f.isAsterisk()) + .filter(Field::hasQualifier) + .map(this::getFamilyQualifierFilter) + .collect(Collectors.toList()); + } + + private Filters.Filter getFamilyQualifierFilter(Field field) { + return FILTERS.chain() + .filter(FILTERS.family().exactMatch(field.getFamily())) + .filter(FILTERS.qualifier().exactMatch(field.getQualifier())); + } + + private Filters.Filter getTimestampFilter(SqlQuery sqlQuery) { + return chain(getTimestampFilters(sqlQuery)); + } + + private Filters.Filter interleave(List filters) { + if (filters.isEmpty()) { + return FILTERS.pass(); + } + if (filters.size() == 1) { + return filters.get(0); + } + var interleaveFilter = FILTERS.interleave(); + for (var filter : filters) { + interleaveFilter.filter(filter); + } + return interleaveFilter; + } + + private Filters.Filter chain(List filters) { + if (filters.isEmpty()) { + return FILTERS.pass(); + } + if (filters.size() == 1) { + return filters.get(0); + } + var filter = FILTERS.chain(); + for (var tsFilter : filters) { + filter.filter(tsFilter); + } + return filter; + } + + private List getTimestampFilters(SqlQuery sqlQuery) { + var filters = new LinkedList(); + for (var where : filterTimestampWhereClauses(sqlQuery)) { + var timestamp = toTimestamp(where.getValue()); + switch (where.getOperator()) { + case EQUAL: + filters.add(FILTERS.timestamp().exact(timestamp)); + break; + case LESS_THAN: + filters.add(FILTERS.timestamp().range().endClosed(timestamp)); + break; + case LESS_THAN_OR_EQUAL: + filters.add(FILTERS.timestamp().range().endOpen(timestamp)); + break; + case GREATER_THAN: + filters.add(FILTERS.timestamp().range().startClosed(timestamp)); + break; + case GREATER_THAN_OR_EQUAL: + filters.add(FILTERS.timestamp().range().startOpen(timestamp)); + break; + case LIKE: + default: + throw new IllegalArgumentException(String.format( + "Operator %s is not supported for timestamps", + where.getOperator())); + } + } + return filters; + } + + private long toTimestamp(Value value) { + long millis; + switch (value.getValueType()) { + case STRING: + millis = DateTimeFormatUtil.toMicros(value.asString()); + break; + case NUMBER: + millis = value.asLong(); + break; + default: throw new IllegalArgumentException( + String.format( + "Could not parse %s to millis. Must be integer or date time string", + value.asString())); + } + return millis; + } +} diff --git a/src/main/java/com/erikmafo/btviewer/sql/QueryType.java b/src/main/java/com/erikmafo/btviewer/sql/QueryType.java new file mode 100644 index 0000000..e7de5ce --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/sql/QueryType.java @@ -0,0 +1,5 @@ +package com.erikmafo.btviewer.sql; + +public enum QueryType { + SELECT +} diff --git a/src/main/java/com/erikmafo/btviewer/sql/ReservedWord.java b/src/main/java/com/erikmafo/btviewer/sql/ReservedWord.java new file mode 100644 index 0000000..b8525ec --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/sql/ReservedWord.java @@ -0,0 +1,65 @@ +package com.erikmafo.btviewer.sql; + +public enum ReservedWord { + + SELECT("SELECT", SqlTokenType.SELECT, true), + ASTERISK("*", SqlTokenType.ASTERISK, true), + COMMA(",", SqlTokenType.COMMA, false), + FROM("FROM", SqlTokenType.FROM, true), + WHERE("WHERE", SqlTokenType.WHERE, true), + AND("AND", SqlTokenType.AND, true), + EQ("=", SqlTokenType.OPERATOR, false), + GEQ(">=", SqlTokenType.OPERATOR, false), + GT(">", SqlTokenType.OPERATOR, false), + LEQ("<=", SqlTokenType.OPERATOR, false), + LT("<", SqlTokenType.OPERATOR, false), + LIKE("LIKE", SqlTokenType.OPERATOR, true), + OPENING_PARENTHESES("(", null, false), + CLOSING_PARENTHESES(")", null, false), + INSERT_INTO("INSERT INTO", null, true), + VALUES("VALUES", null, true), + UPDATE("UPDATE", null, true), + DELETE_FROM("DELETE FROM", null, true), + LIMIT("LIMIT", SqlTokenType.LIMIT, true); + + private final String value; + private final SqlTokenType tokenType; + private final boolean requireWhitespaceAfter; + + ReservedWord(String value, SqlTokenType tokenType, boolean requireWhitespaceAfter) { + this.value = value; + this.tokenType = tokenType; + this.requireWhitespaceAfter = requireWhitespaceAfter; + } + + boolean matchesStartOf(String sql) { + + if (sql.length() < length()) { + return false; + } + + if (requireWhitespaceAfter && + sql.length() > length() && + !Character.isWhitespace(sql.charAt(length()))) { + return false; + } + + return sql.substring(0, length()).equalsIgnoreCase(value); + } + + int length() { + return value.length(); + } + + String value() { + return value; + } + + SqlTokenType tokenType() { + return tokenType; + } + + boolean isSupported() { + return tokenType != null; + } +} diff --git a/src/main/java/com/erikmafo/btviewer/sql/SqlParser.java b/src/main/java/com/erikmafo/btviewer/sql/SqlParser.java new file mode 100644 index 0000000..ff916bb --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/sql/SqlParser.java @@ -0,0 +1,140 @@ +package com.erikmafo.btviewer.sql; + +public class SqlParser { + + public SqlQuery parse(String sql) { + + var tokenizer = new SqlTokenizer(sql); + var queryBuilder = new QueryBuilder(); + var token = tokenizer.next(); + + while (token != null) { + queryBuilder.handleNextToken(token); + token = tokenizer.next(); + } + + return queryBuilder.getSqlQuery(); + } + + private static class QueryBuilder { + + private final SqlQuery sqlQuery = new SqlQuery(); + private SqlParserStep step = SqlParserStep.QUERY_TYPE; + + private Field whereField; + private Operator whereOperator; + private Value whereValue; + + void handleNextToken(SqlToken token) { + if (token.getTokenType().equals(SqlTokenType.INVALID)) { + throw new IllegalArgumentException(token.getError()); + } + + switch (step) { + + case QUERY_TYPE: + if (token.getTokenType() != SqlTokenType.SELECT) { + throw new IllegalArgumentException("Only SELECT queries are supported"); + } + sqlQuery.setQueryType(QueryType.SELECT); + step = SqlParserStep.SELECT_FIELD; + break; + case SELECT_FIELD: + if (token.getTokenType() == SqlTokenType.IDENTIFIER) { + sqlQuery.addField(new Field(token.getValue())); + step = SqlParserStep.SELECT_COMMA; + } else if (token.getTokenType() == SqlTokenType.ASTERISK) { + sqlQuery.addField(new Field(token.getValue())); + step = SqlParserStep.SELECT_FROM; + } + else { + throw new IllegalArgumentException(String.format("Expected a field identifier or '*' but was '%s'", token.getValue())); + } + break; + case SELECT_COMMA: + if (token.getTokenType() == SqlTokenType.COMMA) { + step = SqlParserStep.SELECT_FIELD; + } else if (token.getTokenType() == SqlTokenType.FROM) { + step = SqlParserStep.SELECT_FROM_TABLE; + } else { + throw new IllegalArgumentException(String.format("Expected ',' or 'FROM' but was '%s'", token.getValue())); + } + break; + case SELECT_FROM: + if (token.getTokenType() != SqlTokenType.FROM) { + throw new IllegalArgumentException(String.format("Expected 'FROM' but was ", token.getValue())); + } + step = SqlParserStep.SELECT_FROM_TABLE; + break; + case SELECT_FROM_TABLE: + if (token.getTokenType() == SqlTokenType.IDENTIFIER) { + sqlQuery.setTableName(token.getValue()); + } else if (token.getTokenType() == SqlTokenType.QUOTED_STRING) { + sqlQuery.setTableName(token.getUnquotedValue()); + } else { + throw new IllegalArgumentException(String.format("Expected a table identifier but was '%s'", token.getValue())); + } + step = SqlParserStep.WHERE; + break; + case WHERE: + if (token.getTokenType() == SqlTokenType.WHERE) { + step = SqlParserStep.WHERE_FIELD; + } else if (token.getTokenType() == SqlTokenType.LIMIT) { + step = SqlParserStep.LIMIT_VALUE; + } else { + throw new IllegalArgumentException(String.format("Expected 'WHERE' but was '%s'", token.getValue())); + } + break; + case WHERE_FIELD: + if (token.getTokenType() != SqlTokenType.IDENTIFIER) { + throw new IllegalArgumentException(String.format("Expected a field identifier but was '%s'", token.getValue())); + } + whereField = new Field(token.getValue()); + step = SqlParserStep.WHERE_OPERATOR; + break; + case WHERE_OPERATOR: + if (token.getTokenType() != SqlTokenType.OPERATOR) { + throw new IllegalArgumentException(String.format("Expected an operator but was '%s'", token.getValue())); + } + whereOperator = Operator.of(token.getValue()); + step = SqlParserStep.WHERE_VALUE; + break; + case WHERE_VALUE: + if (token.getTokenType() == SqlTokenType.INTEGER) { + whereValue = new Value(token.getValue(), ValueType.NUMBER); + } + else if (token.getTokenType() == SqlTokenType.QUOTED_STRING) { + whereValue = new Value(token.getUnquotedValue(), ValueType.STRING); + } else { + throw new IllegalArgumentException(String.format("Expected a number or a string but was '%s'", token.getValue())); + } + sqlQuery.addWhereClause(new WhereClause(whereField, whereOperator, whereValue)); + whereField = null; + whereOperator = null; + whereValue = null; + step = SqlParserStep.WHERE_AND; + break; + case WHERE_AND: + if (token.getTokenType() == SqlTokenType.AND) { + step = SqlParserStep.WHERE_FIELD; + } else if (token.getTokenType() == SqlTokenType.LIMIT) { + step = SqlParserStep.LIMIT_VALUE; + } else { + throw new IllegalArgumentException(String.format("Expected 'AND' but was '%s'", token.getValue())); + } + break; + case LIMIT_VALUE: + if (token.getTokenType() != SqlTokenType.INTEGER) { + throw new IllegalArgumentException(String.format("Expected an integer but was '%s'", token.getValue())); + } + sqlQuery.setLimit(token.getValueAsInt()); + break; + } + } + + SqlQuery getSqlQuery() { + return sqlQuery; + } + } + +} diff --git a/src/main/java/com/erikmafo/btviewer/sql/SqlParserStep.java b/src/main/java/com/erikmafo/btviewer/sql/SqlParserStep.java new file mode 100644 index 0000000..ee71b60 --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/sql/SqlParserStep.java @@ -0,0 +1,15 @@ +package com.erikmafo.btviewer.sql; + +public enum SqlParserStep { + QUERY_TYPE, + SELECT_FIELD, + SELECT_COMMA, + SELECT_FROM, + SELECT_FROM_TABLE, + WHERE, + WHERE_FIELD, + WHERE_OPERATOR, + WHERE_VALUE, + WHERE_AND, + LIMIT_VALUE, +} diff --git a/src/main/java/com/erikmafo/btviewer/sql/SqlQuery.java b/src/main/java/com/erikmafo/btviewer/sql/SqlQuery.java new file mode 100644 index 0000000..e3569f5 --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/sql/SqlQuery.java @@ -0,0 +1,58 @@ +package com.erikmafo.btviewer.sql; + +import java.util.ArrayList; +import java.util.List; + +public class SqlQuery { + + public static String getDefaultSql(String tableName) { + return String.format("SELECT * FROM '%s' LIMIT 1000", tableName); + } + + private QueryType queryType; + private String tableName; + private final List fields = new ArrayList<>(); + private final List whereClauses = new ArrayList<>(); + private int limit = 10_000; + + public void addField(Field field) { + fields.add(field); + } + + public void addWhereClause(WhereClause whereClause) { + whereClauses.add(whereClause); + } + + public QueryType getQueryType() { + return queryType; + } + + public void setQueryType(QueryType queryType) { + this.queryType = queryType; + } + + public String getTableName() { + return tableName; + } + + public void setTableName(String tableName) { + + this.tableName = tableName; + } + + public List getFields() { + return fields; + } + + public List getWhereClauses() { + return whereClauses; + } + + public void setLimit(int limit) { + this.limit = limit; + } + + public int getLimit() { + return limit; + } +} diff --git a/src/main/java/com/erikmafo/btviewer/sql/SqlToken.java b/src/main/java/com/erikmafo/btviewer/sql/SqlToken.java new file mode 100644 index 0000000..e24c841 --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/sql/SqlToken.java @@ -0,0 +1,57 @@ +package com.erikmafo.btviewer.sql; + +public class SqlToken { + + private final String value; + private final SqlTokenType tokenType; + private final int end; + private final String error; + + public SqlToken(String value, SqlTokenType tokenType, int end) { + this(value, tokenType, end, null); + } + + public SqlToken(String value, SqlTokenType tokenType, int end, String error) { + + if (value == null || value.isEmpty()) { + throw new IllegalArgumentException("'value' cannot be null or empty"); + } + + if (tokenType == null) { + throw new NullPointerException("'tokenType' cannot be null"); + } + + this.value = value; + this.tokenType = tokenType; + this.end = end; + this.error = error; + } + + public String getValue() { + return value; + } + + public String getUnquotedValue() { + if (SqlTokenType.QUOTED_STRING == getTokenType()) { + return value.substring(1, value.length() - 1); + } + return value; + } + + public int getValueAsInt() { + return Integer.parseInt(value); + } + + public SqlTokenType getTokenType() { + return tokenType; + } + + public int getStart() { return end - value.length(); } + + public int getEnd() { return end; } + + + public String getError() { + return error; + } +} diff --git a/src/main/java/com/erikmafo/btviewer/sql/SqlTokenType.java b/src/main/java/com/erikmafo/btviewer/sql/SqlTokenType.java new file mode 100644 index 0000000..a796fd3 --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/sql/SqlTokenType.java @@ -0,0 +1,17 @@ +package com.erikmafo.btviewer.sql; + +public enum SqlTokenType { + INVALID, + SELECT, + IDENTIFIER, + ASTERISK, + COMMA, + QUOTED_STRING, + INTEGER, + BOOL, + FROM, + WHERE, + OPERATOR, + AND, + LIMIT, +} diff --git a/src/main/java/com/erikmafo/btviewer/sql/SqlTokenizer.java b/src/main/java/com/erikmafo/btviewer/sql/SqlTokenizer.java new file mode 100644 index 0000000..4a671d1 --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/sql/SqlTokenizer.java @@ -0,0 +1,78 @@ +package com.erikmafo.btviewer.sql; + +import java.util.regex.Pattern; + +public class SqlTokenizer { + + private static final char QUOTE = '\''; + private static final Pattern IDENTIFIER_PATTERN = Pattern.compile("^([A-Za-z]+\\w*)(\\.[A-Za-z]+\\w*)?\\b"); + private static final Pattern INTEGER_PATTERN = Pattern.compile("^(0|[1-9][0-9]*)\\b"); + private static final Pattern BOOL_PATTERN = Pattern.compile("^(?i)(true|false)\\b"); + + private final String sql; + + private int position; + + public SqlTokenizer(String sql) { + this.sql = sql; + } + + public SqlToken next() { + + if (position >= sql.length()) { + return null; + } + + if (Character.isWhitespace(sql.charAt(position))) { + position += 1; + return next(); + } + + var remainingSql = sql.substring(position); + + if (sql.charAt(position) == QUOTE) { + var indexOfNextQuote = sql.indexOf(QUOTE, position + 1); + if (indexOfNextQuote < 0) { + position = sql.length(); + return new SqlToken(remainingSql, SqlTokenType.INVALID, position, String.format("Expected a matching '%s'", QUOTE)); + } + var quotedString = sql.substring(position, indexOfNextQuote + 1); + position += quotedString.length(); + return new SqlToken(quotedString, SqlTokenType.QUOTED_STRING, position); + } + + for (var word : ReservedWord.values()) { + if (word.matchesStartOf(remainingSql)) { + position += word.length(); + if (!word.isSupported()) { + return new SqlToken(word.value(), SqlTokenType.INVALID, position, String.format("'%s' is not supported", word.value())); + } + return new SqlToken(word.value(), word.tokenType(), position); + } + } + + var matcher = IDENTIFIER_PATTERN.matcher(remainingSql); + if (matcher.find()) { + var token = remainingSql.substring(matcher.start(), matcher.end()); + position += token.length(); + return new SqlToken(token, SqlTokenType.IDENTIFIER, position); + } + + matcher = BOOL_PATTERN.matcher(remainingSql); + if (matcher.find()) { + var token = remainingSql.substring(matcher.start(), matcher.end()); + position += token.length(); + return new SqlToken(token, SqlTokenType.BOOL, position); + } + + matcher = INTEGER_PATTERN.matcher(remainingSql); + if (matcher.find()) { + var token = remainingSql.substring(matcher.start(), matcher.end()); + position += token.length(); + return new SqlToken(token, SqlTokenType.INTEGER, position); + } + + position = sql.length(); + return new SqlToken(remainingSql, SqlTokenType.INVALID, position, String.format("Unable to parse: '%s'", remainingSql)); + } +} diff --git a/src/main/java/com/erikmafo/btviewer/sql/Value.java b/src/main/java/com/erikmafo/btviewer/sql/Value.java new file mode 100644 index 0000000..df230eb --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/sql/Value.java @@ -0,0 +1,49 @@ +package com.erikmafo.btviewer.sql; + +public class Value { + + private final String value; + private final ValueType valueType; + + public Value(String value, ValueType valueType) { + + if (value == null || value.isEmpty()) { + throw new IllegalArgumentException("'value' cannot be null or empty"); + } + + if (valueType == null) { + throw new NullPointerException("'valueType' cannot be null"); + } + + this.value = value; + this.valueType = valueType; + } + + public String asString() { + return value; + } + + public double asDouble() { + return Double.parseDouble(value); + } + + public float asFloat() { + return Float.parseFloat(value); + } + + public int asInt() { + return Integer.parseInt(value); + } + + public boolean asBool() { + return Boolean.parseBoolean(value); + } + + public long asLong() { + return Long.parseLong(value); + } + + public ValueType getValueType() { + return valueType; + } +} diff --git a/src/main/java/com/erikmafo/btviewer/sql/ValueType.java b/src/main/java/com/erikmafo/btviewer/sql/ValueType.java new file mode 100644 index 0000000..929c473 --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/sql/ValueType.java @@ -0,0 +1,6 @@ +package com.erikmafo.btviewer.sql; + +public enum ValueType { + STRING, + NUMBER, +} diff --git a/src/main/java/com/erikmafo/btviewer/sql/WhereClause.java b/src/main/java/com/erikmafo/btviewer/sql/WhereClause.java new file mode 100644 index 0000000..b96b15c --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/sql/WhereClause.java @@ -0,0 +1,39 @@ +package com.erikmafo.btviewer.sql; + +public class WhereClause { + + private final Field field; + private final Operator operator; + private final Value value; + + public WhereClause(Field field, Operator operator, Value value) { + + if (field == null) { + throw new NullPointerException("'field' cannot be null"); + } + + if (operator == null) { + throw new NullPointerException("'operator' cannot be null"); + } + + if (value == null) { + throw new NullPointerException("'value' cannot be null"); + } + + this.field = field; + this.operator = operator; + this.value = value; + } + + public Field getField() { + return field; + } + + public Operator getOperator() { + return operator; + } + + public Value getValue() { + return value; + } +} diff --git a/src/main/java/com/erikmafo/btviewer/util/AlertUtil.java b/src/main/java/com/erikmafo/btviewer/util/AlertUtil.java new file mode 100644 index 0000000..85169a0 --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/util/AlertUtil.java @@ -0,0 +1,18 @@ +package com.erikmafo.btviewer.util; + +import javafx.concurrent.WorkerStateEvent; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; + +public class AlertUtil { + + public static void displayError(String errorText, WorkerStateEvent event) { + displayError(errorText, event.getSource().getException()); + } + + public static void displayError(String errorText, Throwable cause) { + var alert = new Alert(Alert.AlertType.ERROR, errorText + ": " + cause.getLocalizedMessage(), ButtonType.CLOSE); + alert.setTitle("Computer says no!"); + alert.showAndWait(); + } +} diff --git a/src/main/java/com/erikmafo/btviewer/util/ByteStringConverterUtil.java b/src/main/java/com/erikmafo/btviewer/util/ByteStringConverterUtil.java new file mode 100644 index 0000000..d1d1169 --- /dev/null +++ b/src/main/java/com/erikmafo/btviewer/util/ByteStringConverterUtil.java @@ -0,0 +1,45 @@ +package com.erikmafo.btviewer.util; + +import com.google.protobuf.ByteString; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class ByteStringConverterUtil { + + public static ByteString toByteString(String stringUtf8) { + return ByteString.copyFromUtf8(stringUtf8); + } + + public static ByteString toByteString(long value) { + var buffer = ByteBuffer + .allocate(Long.SIZE / Byte.SIZE) + .putLong(value) + .order(ByteOrder.BIG_ENDIAN); + return ByteString.copyFrom(buffer.array()); + } + + public static ByteString toByteString(int value) { + var buffer = ByteBuffer + .allocate(Integer.SIZE / Byte.SIZE) + .putInt(value) + .order(ByteOrder.BIG_ENDIAN); + return ByteString.copyFrom(buffer.array()); + } + + public static ByteString toByteString(float value) { + var buffer = ByteBuffer + .allocate(Float.SIZE / Byte.SIZE) + .putFloat(value) + .order(ByteOrder.BIG_ENDIAN); + return ByteString.copyFrom(buffer.array()); + } + + public static ByteString toByteString(double value) { + var buffer = ByteBuffer + .allocate(Double.SIZE / Byte.SIZE) + .putDouble(value) + .order(ByteOrder.BIG_ENDIAN); + return ByteString.copyFrom(buffer.array()); + } +} diff --git a/src/main/resources/config.properties b/src/main/resources/config.properties index 03e0bf6..dd76143 100644 --- a/src/main/resources/config.properties +++ b/src/main/resources/config.properties @@ -1,3 +1,2 @@ USE_BIGTABLE_EMULATOR = false -USE_IN_MEMORY_TABLE_CONFIG_MANAGER = false -USE_IN_MEMORY_INSTANCE_MANAGER = false \ No newline at end of file +USE_IN_MEMORY_DATABASE = false \ No newline at end of file diff --git a/src/main/resources/css/main.css b/src/main/resources/css/main.css index 8b13789..5c912f5 100644 --- a/src/main/resources/css/main.css +++ b/src/main/resources/css/main.css @@ -1 +1,7 @@ +.pane-view { + -fx-padding: 10 10 10 10; +} +.has-spacing { + -fx-spacing: 5; +} \ No newline at end of file diff --git a/src/main/resources/css/query_box.css b/src/main/resources/css/query_box.css new file mode 100644 index 0000000..1fafe5f --- /dev/null +++ b/src/main/resources/css/query_box.css @@ -0,0 +1,17 @@ +@import "main.css" + +.keyword { + -fx-fill: blue; + -fx-font-weight: bold; +} +.string { + -fx-fill: purple; +} + +.comment { + -fx-fill: green; +} + +.paragraph-box:has-caret { + -fx-background-color: #f2f9fc; +} \ No newline at end of file diff --git a/src/main/resources/fxml/bigtable_instance_dialog.fxml b/src/main/resources/fxml/add_instance_dialog.fxml similarity index 100% rename from src/main/resources/fxml/bigtable_instance_dialog.fxml rename to src/main/resources/fxml/add_instance_dialog.fxml diff --git a/src/main/resources/fxml/bigtable_table_view.fxml b/src/main/resources/fxml/bigtable_table_view.fxml deleted file mode 100644 index 2995177..0000000 --- a/src/main/resources/fxml/bigtable_table_view.fxml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/fxml/bigtable_view.fxml b/src/main/resources/fxml/bigtable_view.fxml new file mode 100644 index 0000000..f97d73c --- /dev/null +++ b/src/main/resources/fxml/bigtable_view.fxml @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/fxml/project_item_menu.fxml b/src/main/resources/fxml/project_item_menu.fxml new file mode 100644 index 0000000..67e5bbd --- /dev/null +++ b/src/main/resources/fxml/project_item_menu.fxml @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/src/main/resources/fxml/query_box.fxml b/src/main/resources/fxml/query_box.fxml new file mode 100644 index 0000000..8fa4029 --- /dev/null +++ b/src/main/resources/fxml/query_box.fxml @@ -0,0 +1,23 @@ + + + + + + + + + +