Added migration info to README.md, and added ability to insert default categories into existing perfin profiles.
This commit is contained in:
parent
0fe451029d
commit
396fd122a8
27
README.md
27
README.md
|
@ -37,3 +37,30 @@ to set the version everywhere that it needs to be.
|
||||||
|
|
||||||
Once that's done, the workflow will start, and you should see a release appear
|
Once that's done, the workflow will start, and you should see a release appear
|
||||||
in the next few minutes.
|
in the next few minutes.
|
||||||
|
|
||||||
|
## Migration Procedure
|
||||||
|
|
||||||
|
Because this application relies on a structured relational database schema,
|
||||||
|
changes to the schema must be handled with care to avoid destroying users' data.
|
||||||
|
Specifically, when changes are made to the schema, a *migration* must be defined
|
||||||
|
which provides instructions for Perfin to safely apply changes to an old schema.
|
||||||
|
|
||||||
|
The database schema is versioned using whole-number versions (1, 2, 3, ...), and
|
||||||
|
a migration is defined for each transition from version to version, such that
|
||||||
|
any older version can be incrementally upgraded, step by step, to the latest
|
||||||
|
schema version.
|
||||||
|
|
||||||
|
Perfin only supports the latest schema version, as defined by `JdbcDataSourceFactory.SCHEMA_VERSION`.
|
||||||
|
When the app loads a profile, it'll check that profile's schema version by
|
||||||
|
reading a `.jdbc-schema-version.txt` file in the profile's main directory. If
|
||||||
|
the profile's schema version is **less than** the current, Perfin will
|
||||||
|
ask the user if they want to upgrade. If the profile's schema version is
|
||||||
|
**greater than** the current, Perfin will tell the user that it can't load a
|
||||||
|
schema from a newer version, and will prompt the user to upgrade.
|
||||||
|
|
||||||
|
### Writing a Migration
|
||||||
|
|
||||||
|
1. Write your migration. This can be plain SQL (placed in `resources/sql/migration`), or Java code.
|
||||||
|
2. Add your migration to `com.andrewlalis.perfin.data.impl.migration.Migrations#getMigrations()`.
|
||||||
|
3. Increment the schema version defined in `JdbcDataSourceFactory`.
|
||||||
|
4. Test the migration yourself on a profile with data.
|
||||||
|
|
|
@ -2,6 +2,9 @@ package com.andrewlalis.perfin.control;
|
||||||
|
|
||||||
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
|
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
|
||||||
import com.andrewlalis.perfin.data.TransactionCategoryRepository;
|
import com.andrewlalis.perfin.data.TransactionCategoryRepository;
|
||||||
|
import com.andrewlalis.perfin.data.impl.JdbcDataSource;
|
||||||
|
import com.andrewlalis.perfin.data.impl.JdbcDataSourceFactory;
|
||||||
|
import com.andrewlalis.perfin.data.util.DbUtil;
|
||||||
import com.andrewlalis.perfin.model.Profile;
|
import com.andrewlalis.perfin.model.Profile;
|
||||||
import com.andrewlalis.perfin.view.BindingUtil;
|
import com.andrewlalis.perfin.view.BindingUtil;
|
||||||
import com.andrewlalis.perfin.view.component.CategoryTile;
|
import com.andrewlalis.perfin.view.component.CategoryTile;
|
||||||
|
@ -11,6 +14,9 @@ import javafx.collections.ObservableList;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
|
||||||
import static com.andrewlalis.perfin.PerfinApp.router;
|
import static com.andrewlalis.perfin.PerfinApp.router;
|
||||||
|
|
||||||
public class CategoriesViewController implements RouteSelectionListener {
|
public class CategoriesViewController implements RouteSelectionListener {
|
||||||
|
@ -36,4 +42,22 @@ public class CategoriesViewController implements RouteSelectionListener {
|
||||||
TransactionCategoryRepository::findTree
|
TransactionCategoryRepository::findTree
|
||||||
).thenAccept(nodes -> Platform.runLater(() -> categoryTreeNodes.setAll(nodes)));
|
).thenAccept(nodes -> Platform.runLater(() -> categoryTreeNodes.setAll(nodes)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML public void addDefaultCategories() {
|
||||||
|
boolean confirm = Popups.confirm(categoriesVBox, "Are you sure you want to add all of Perfin's default categories to your profile? This might interfere with existing categories of the same name.");
|
||||||
|
if (!confirm) return;
|
||||||
|
JdbcDataSource ds = (JdbcDataSource) Profile.getCurrent().dataSource();
|
||||||
|
try (var conn = ds.getConnection()) {
|
||||||
|
DbUtil.doTransaction(conn, () -> {
|
||||||
|
try {
|
||||||
|
new JdbcDataSourceFactory().insertDefaultCategories(conn);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
refreshCategories();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Popups.error(categoriesVBox, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,7 +100,18 @@ public class JdbcDataSourceFactory implements DataSourceFactory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void insertDefaultData(Connection conn) throws IOException, SQLException {
|
/**
|
||||||
|
* Inserts all default data into the database, using static content found in
|
||||||
|
* various locations on the classpath.
|
||||||
|
* @param conn The connection to use to insert data.
|
||||||
|
* @throws IOException If resources couldn't be read.
|
||||||
|
* @throws SQLException If SQL fails.
|
||||||
|
*/
|
||||||
|
public void insertDefaultData(Connection conn) throws IOException, SQLException {
|
||||||
|
insertDefaultCategories(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insertDefaultCategories(Connection conn) throws IOException, SQLException {
|
||||||
try (
|
try (
|
||||||
var categoriesIn = JdbcDataSourceFactory.class.getResourceAsStream("/sql/data/default-categories.json");
|
var categoriesIn = JdbcDataSourceFactory.class.getResourceAsStream("/sql/data/default-categories.json");
|
||||||
var stmt = conn.prepareStatement(
|
var stmt = conn.prepareStatement(
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
</StyledText>
|
</StyledText>
|
||||||
<HBox styleClass="std-padding, std-spacing" VBox.vgrow="NEVER">
|
<HBox styleClass="std-padding, std-spacing" VBox.vgrow="NEVER">
|
||||||
<Button text="Add Category" onAction="#addCategory"/>
|
<Button text="Add Category" onAction="#addCategory"/>
|
||||||
|
<Button text="Add Default Categories" onAction="#addDefaultCategories"/>
|
||||||
</HBox>
|
</HBox>
|
||||||
<ScrollPane styleClass="tile-container-scroll" VBox.vgrow="ALWAYS">
|
<ScrollPane styleClass="tile-container-scroll" VBox.vgrow="ALWAYS">
|
||||||
<VBox fx:id="categoriesVBox" styleClass="tile-container"/>
|
<VBox fx:id="categoriesVBox" styleClass="tile-container"/>
|
||||||
|
|
Loading…
Reference in New Issue