Added migration info to README.md, and added ability to insert default categories into existing perfin profiles.

This commit is contained in:
Andrew Lalis 2024-02-03 23:27:23 -05:00
parent 0fe451029d
commit 396fd122a8
4 changed files with 64 additions and 1 deletions

View File

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

View File

@ -2,6 +2,9 @@ package com.andrewlalis.perfin.control;
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
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.view.BindingUtil;
import com.andrewlalis.perfin.view.component.CategoryTile;
@ -11,6 +14,9 @@ import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.layout.VBox;
import java.io.IOException;
import java.io.UncheckedIOException;
import static com.andrewlalis.perfin.PerfinApp.router;
public class CategoriesViewController implements RouteSelectionListener {
@ -36,4 +42,22 @@ public class CategoriesViewController implements RouteSelectionListener {
TransactionCategoryRepository::findTree
).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);
}
}
}

View File

@ -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 (
var categoriesIn = JdbcDataSourceFactory.class.getResourceAsStream("/sql/data/default-categories.json");
var stmt = conn.prepareStatement(

View File

@ -24,6 +24,7 @@
</StyledText>
<HBox styleClass="std-padding, std-spacing" VBox.vgrow="NEVER">
<Button text="Add Category" onAction="#addCategory"/>
<Button text="Add Default Categories" onAction="#addDefaultCategories"/>
</HBox>
<ScrollPane styleClass="tile-container-scroll" VBox.vgrow="ALWAYS">
<VBox fx:id="categoriesVBox" styleClass="tile-container"/>