๐Ÿ—„๏ธ Core Concepts

Migrations & Metadata Mapping

This guide covers how to create custom field mappings and metadata transformations in PikaORM, allowing you to handle complex data types, custom serialization, and specialized database storage requirements.

๐Ÿท๏ธtoColumn()
๐Ÿ”„transformForDB()
โ†ฉ๏ธtransformFromDB()
๐Ÿ”‘asId()
๐Ÿ”ขasVersionColumn()

Core Mapping Components

The FieldMapping class is the core component that defines how a field maps to a database column:

public class FieldMapping {
    // Core configuration methods
    FieldMapping toColumn(String columnName)            // Set custom column name
    FieldMapping asType(Class<?> dbClass)               // Set database storage type
    FieldMapping asId()                                 // Mark as ID column
    FieldMapping asVersionColumn()                      // Mark as version column

    // Data transformation methods
    FieldMapping transformForDB(UnaryOperator<Object> func)     // Java โ†’ Database
    FieldMapping transformFromDB(UnaryOperator<Object> func)    // Database โ†’ Java
    FieldMapping withVersionIncrementer(UnaryOperator<Object> incrementer)
}

Custom Mapping Examples

JSON Field Mapping

Store Java objects as JSON strings in the database using GSON:

public class HasCustomizedMetadata {

    @Override
    public void migrations() {
        add(this::customMetadataExample);
    }

    public PikaMigration customMetadataExample() {
        return makeMigration("customMetadataExample")
            .up("""
                CREATE TABLE IF NOT EXISTS foos (
                    id   INTEGER PRIMARY KEY,
                    json TEXT
                );
            """)
            .down("DROP TABLE foos;");
    }

    public static Mapping mapping() {
        Gson gson = new Gson();
        return new Mapping() {
            @Override
            public String mapToTable() { return "foos"; }

            @Override
            public FieldMapping mapField(Field field) {
                return switch (field.getName()) {
                    case "ignoreMe" -> ignore(field);
                    case "myId"     -> map(field).toColumn("id").asId();
                    case "json"     -> map(field).asType(String.class)
                                          .transformForDB(gson::toJson)
                                          .transformFromDB(val -> gson.fromJson(
                                              String.valueOf(val), Map.class));
                    default         -> defaultMapping(field);
                };
            }
        };
    }

    String ignoreMe;
    long myId;
    Map json;
    // Getters and Setters...
}

All customized mapping is done by overriding the methods for metadata mapping with the Mapping class and FieldMapping. We recommend using switch and case statements for better readability with your custom classes.

Usage with ORM

The ORM will automatically detect and use your inner Mapping class when initializing the schema or manipulating records.

@Test
public void testCustomMapping() {
    PikaORM orm = new PikaORM("jdbc:sqlite:web.db")
        .withLogLevel(TRACE)
        .makeDefaultORM();

    // The ORM automatically detects and uses the inner Mapping class
    HasCustomizedMetadata entity = new HasCustomizedMetadata();
    entity.setMap(Map.of("foo", 1.0, "bar", 2.0));

    orm.insert(entity);

    var retrieved = orm.find(HasCustomizedMetadata.class).byId(entity.getId());
}

Overall the sky is the limit! We can't wait to see what kind of crazy migrations and custom metadata architecture the community could create โ€” we hope these examples can point you in the right direction.