123 lines
4 KiB
Go
123 lines
4 KiB
Go
|
package tdb
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
|
||
|
"git.keganmyers.com/terribleplan/tdb/stringy"
|
||
|
|
||
|
bolt "go.etcd.io/bbolt"
|
||
|
)
|
||
|
|
||
|
type foreignConstraint struct {
|
||
|
domestic *table
|
||
|
foreign *table
|
||
|
field dbField
|
||
|
index indexish
|
||
|
}
|
||
|
type foreignSimpleConstraint foreignConstraint
|
||
|
type foreignArrayConstraint foreignConstraint
|
||
|
|
||
|
func validateForeignRaw(b *bolt.Bucket, foreignKey []byte) ConstraintValidationStatus {
|
||
|
if b.Get(foreignKey) == nil {
|
||
|
return ConstraintViolation
|
||
|
}
|
||
|
return ConstraintValidated
|
||
|
}
|
||
|
|
||
|
func newSimpleForeignConstraint(domestic *table, foreign string, field dbField, index indexish) (constraintish, error) {
|
||
|
if domestic == nil {
|
||
|
return nil, errors.New("[constraint] [foreign] unable to create: no domestic table")
|
||
|
}
|
||
|
|
||
|
if !field.IsUint64() {
|
||
|
return nil, fmt.Errorf("[constraint] [foreign] unable to create: '%s'.'%s' is not a uint64", domestic.name, field.Name)
|
||
|
}
|
||
|
|
||
|
if foreign == "" {
|
||
|
return nil, errors.New("[constraint] [foreign] unable to create: no foreign table")
|
||
|
}
|
||
|
foreignTable, ok := domestic.db.tables[foreign]
|
||
|
if !ok {
|
||
|
return nil, fmt.Errorf("[constraint] [foreign] unable to create: no such table '%s'", foreign)
|
||
|
}
|
||
|
|
||
|
if index == nil {
|
||
|
domestic.debugLogf("[constraint] [foreign] warning: creating constraint on '%s'.'%s' without index. will not check when foreign records are removed (to avoid table scan)", domestic.name, field.Name)
|
||
|
}
|
||
|
|
||
|
return &foreignSimpleConstraint{
|
||
|
domestic: domestic,
|
||
|
foreign: foreignTable,
|
||
|
field: field,
|
||
|
index: index,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (c *foreignSimpleConstraint) validate(tx *Tx, pv dbPtrValue) error { // foreign keys must all be uint64, those are the only supported primary keys
|
||
|
foreignId := pv.dangerous_Field(c.field).Uint()
|
||
|
|
||
|
// the foreign constraint is not responsible for enforcing nullability
|
||
|
if foreignId == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
foreignKey := []byte(stringy.LiteralUintToString(foreignId))
|
||
|
if validateForeignRaw(c.foreign.bucket(tx), foreignKey) == ConstraintViolation {
|
||
|
c.domestic.debugLogf("[constraint] [foreign] violation: '%s' with Id '%s' does not exist", c.foreign.name, foreignKey)
|
||
|
return fmt.Errorf("[constraint] [foreign] violation: '%s' with Id '%s' does not exist", c.foreign.name, foreignKey)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func newArrayForeignConstraint(domestic *table, foreign string, field dbField, index indexish) (constraintish, error) {
|
||
|
if domestic == nil {
|
||
|
return nil, errors.New("[constraint] [foreign] unable to create: no domestic table")
|
||
|
}
|
||
|
|
||
|
if !field.IsUint64Slice() {
|
||
|
return nil, fmt.Errorf("[constraint] [foreign] unable to create: '%s'.'%s' is not a uint64 array", domestic.name, field.Name)
|
||
|
}
|
||
|
|
||
|
if foreign == "" {
|
||
|
return nil, errors.New("[constraint] [foreign] unable to create: no foreign table")
|
||
|
}
|
||
|
foreignTable, ok := domestic.db.tables[foreign]
|
||
|
if !ok {
|
||
|
return nil, fmt.Errorf("[constraint] [foreign] unable to create: no such table '%s'", foreign)
|
||
|
}
|
||
|
|
||
|
if index == nil {
|
||
|
domestic.debugLogf("[constraint] [foreign] warning: creating constraint on '%s'.'%s' without index. will not check when foreign records are removed (to avoid table scan)", domestic.name, field.Name)
|
||
|
}
|
||
|
|
||
|
return &foreignArrayConstraint{
|
||
|
domestic: domestic,
|
||
|
foreign: foreignTable,
|
||
|
field: field,
|
||
|
index: index,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (c *foreignArrayConstraint) validate(tx *Tx, pv dbPtrValue) error { // foreign keys must all be uint64, those are the only supported primary keys
|
||
|
foreignIds := pv.dangerous_Field(c.field)
|
||
|
|
||
|
foreignIdsLen := foreignIds.Len()
|
||
|
for i := 0; i < foreignIdsLen; i++ {
|
||
|
foreignId := foreignIds.Index(i).Uint()
|
||
|
// the foreign constraint is not responsible for enforcing nullability
|
||
|
if foreignId == 0 {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
foreignKey := []byte(stringy.LiteralUintToString(foreignId))
|
||
|
if validateForeignRaw(c.foreign.bucket(tx), foreignKey) == ConstraintViolation {
|
||
|
c.domestic.debugLogf("[constraint] [foreign] violation: '%s' with Id '%s' does not exist", c.foreign.name, foreignKey)
|
||
|
return fmt.Errorf("[constraint] [foreign] violation: '%s' with Id '%s' does not exist", c.foreign.name, foreignKey)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
|
||
|
}
|