We're updating the issue view to help you get more done. 

Columns in the Corda database schema should have correct NULL/NOT NULL constraints.

Description

The Hibernate @Column annotation has the following default values:

  • nullable = true

  • insertable = true

  • updateable = true

So Hibernate expects to be able to insert NULL values into the database by default. However, Corda's Kotlin entity classes do not always set nullable=false on their columns, even when the Kotlin type explicitly disallows null values. E.g. the VaultStates entity:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 @Entity @Table(name = "vault_states", indexes = arrayOf(Index(name = "state_status_idx", columnList = "state_status"), Index(name = "lock_id_idx", columnList = "lock_id, state_status"))) class VaultStates( /** NOTE: serialized transaction state (including contract state) is now resolved from transaction store */ // TODO: create a distinct table to hold serialized state data (once DBTransactionStore is encrypted) /** refers to the X500Name of the notary a state is attached to */ @Column(name = "notary_name") var notary: Party, /** references a concrete ContractState that is [QueryableState] and has a [MappedSchema] */ @Column(name = "contract_state_class_name") var contractStateClassName: String, /** state lifecycle: unconsumed, consumed */ @Column(name = "state_status") var stateStatus: Vault.StateStatus, /** refers to timestamp recorded upon entering UNCONSUMED state */ @Column(name = "recorded_timestamp") var recordedTime: Instant, /** refers to timestamp recorded upon entering CONSUMED state */ @Column(name = "consumed_timestamp", nullable = true) var consumedTime: Instant? = null, /** used to denote a state has been soft locked (to prevent double spend) * will contain a temporary unique [UUID] obtained from a flow session */ @Column(name = "lock_id", nullable = true) var lockId: String? = null, /** refers to the last time a lock was taken (reserved) or updated (released, re-reserved) */ @Column(name = "lock_timestamp", nullable = true) var lockUpdateTime: Instant? = null ) : PersistentState()

This entity seems to expect @Colum(nullable=false) by default as it never sets this flag when it needs to and only sets it when it doesn't.

Similarly, AbstractCashSelection.attemptSpend() is clearly (and incorrectly) assuming that it can never read NULL values from the database:

1 2 3 4 5 6 7 while (rs.next()) { val txHash = SecureHash.parse(rs.getString(1)) val index = rs.getInt(2) val pennies = rs.getLong(3) totalPennies = rs.getLong(4) val rowLockId = rs.getString(5) ...

because getLong() and getInt() both return Java primitive types that are zero for NULL values. Developers are expected to call wasNull() to discover if the column was NULL or not.

Our Hibernate entities need to impose the correct NULL/NOT NULL constraints on the database - whatever these correct values actually are.

Status

Assignee

Maksymilian Pawlak

Reporter

Chris Rankin

Priority

Medium

Labels

Severity

Medium

Fix versions

Ported to...

None

Feature Team

Corda Core