Add Transaction Properties #15
			
				
			
		
		
		
	
							
								
								
									
										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
 | 
			
		||||
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.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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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"/>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue