Skip to main content

Defaulted Types

When inserting rows, some columns have database defaults (auto-increment IDs, timestamps, etc.). The Defaulted type lets you explicitly choose: provide a value, or use the database default.

The Defaulted Type​

Typr generates a Defaulted<T> type in your project:

Defaulted
/** This signals a value where if you don't provide it, postgres will generate it for you */
public sealed interface Defaulted<T> permits Defaulted.Provided, Defaulted.UseDefault {
record Provided<T>(T value) implements Defaulted<T> {
public Provided<T> withValue(T value) {
return new Provided<>(value);
}

@Override
public <U> U fold(
java.util.function.Supplier<U> onDefault, java.util.function.Function<T, U> onProvided) {
return onProvided.apply(value);
}

@Override
public T getOrElse(java.util.function.Supplier<T> onDefault) {
return value;
}

@Override
public void visit(java.lang.Runnable onDefault, java.util.function.Consumer<T> onProvided) {
onProvided.accept(value);
}
}

record UseDefault<T>() implements Defaulted<T> {
@Override
public <U> U fold(
java.util.function.Supplier<U> onDefault, java.util.function.Function<T, U> onProvided) {
return onDefault.get();
}

@Override
public T getOrElse(java.util.function.Supplier<T> onDefault) {
return onDefault.get();
}

@Override
public void visit(java.lang.Runnable onDefault, java.util.function.Consumer<T> onProvided) {
onDefault.run();
}
}
  • Provided(value) - Use this specific value
  • UseDefault - Let the database generate the value

Unsaved Row Types​

For tables with default columns, Typr generates an "Unsaved" row type:

CompanyRowUnsaved
/** This class corresponds to a row in table `showcase.company` which has not been persisted yet */
public record CompanyRowUnsaved(
CompanyId id,
String name,
Optional<Integer> foundedYear,
/** Default: true */
Defaulted<Optional<Boolean>> active,
/** Default: CURRENT_TIMESTAMP */
Defaulted<Optional<LocalDateTime>> createdAt) {
public CompanyRowUnsaved(CompanyId id, String name) {
this(id, name, Optional.empty(), new UseDefault<>(), new UseDefault<>());
}

Notice how active and createdAt are wrapped in Defaulted—they have database defaults. The id, name, and foundedYear columns don't have defaults, so they're required.

Using Defaulted​

Insert with defaults:

// Java - use database defaults
var unsaved = new CompanyRowUnsaved(
new CompanyId(1),
"Acme Corp"
);
CompanyRow saved = repo.insert(unsaved, conn);
// saved.active() will be true (the database default)
// saved.createdAt() will be the current timestamp
// Kotlin - use database defaults
val unsaved = CompanyRowUnsaved(
id = CompanyId(1),
name = "Acme Corp"
)
val saved = repo.insert(unsaved, conn)

Override a default:

// Java - provide a specific value
var unsaved = new CompanyRowUnsaved(
new CompanyId(1),
"Acme Corp",
Optional.empty(),
new Defaulted.Provided<>(Optional.of(false)), // Override active
new Defaulted.UseDefault<>() // Use default for createdAt
);

Repository Methods​

Repositories accept both regular rows and unsaved rows:

CompanyRepo
  CompanyRow insert(CompanyRow unsaved, Connection c);

CompanyRow insert(CompanyRowUnsaved unsaved, Connection c);

Long insertStreaming(Iterator<CompanyRow> unsaved, Integer batchSize, Connection c);

/** NOTE: this functionality requires PostgreSQL 16 or later! */
Long insertUnsavedStreaming(Iterator<CompanyRowUnsaved> unsaved, Integer batchSize, Connection c);