2023-10-19 10:19:10 +00:00
package eventstore
import (
"context"
"database/sql"
_ "embed"
"errors"
"fmt"
"strings"
"github.com/jackc/pgconn"
"github.com/zitadel/logging"
errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed unique_constraints_delete.sql
deleteConstraintStmt string
//go:embed unique_constraints_add.sql
addConstraintStmt string
)
func handleUniqueConstraints ( ctx context . Context , tx * sql . Tx , commands [ ] eventstore . Command ) error {
deletePlaceholders := make ( [ ] string , 0 )
deleteArgs := make ( [ ] any , 0 )
addPlaceholders := make ( [ ] string , 0 )
addArgs := make ( [ ] any , 0 )
addConstraints := map [ string ] * eventstore . UniqueConstraint { }
deleteConstraints := map [ string ] * eventstore . UniqueConstraint { }
for _ , command := range commands {
for _ , constraint := range command . UniqueConstraints ( ) {
switch constraint . Action {
case eventstore . UniqueConstraintAdd :
2023-11-23 05:15:40 +00:00
constraint . UniqueField = strings . ToLower ( constraint . UniqueField )
2023-10-19 10:19:10 +00:00
addPlaceholders = append ( addPlaceholders , fmt . Sprintf ( "($%d, $%d, $%d)" , len ( addArgs ) + 1 , len ( addArgs ) + 2 , len ( addArgs ) + 3 ) )
addArgs = append ( addArgs , command . Aggregate ( ) . InstanceID , constraint . UniqueType , constraint . UniqueField )
2023-10-27 12:10:01 +00:00
addConstraints [ fmt . Sprintf ( uniqueConstraintPlaceholderFmt , command . Aggregate ( ) . InstanceID , constraint . UniqueType , constraint . UniqueField ) ] = constraint
2023-10-19 10:19:10 +00:00
case eventstore . UniqueConstraintRemove :
2023-11-23 05:15:40 +00:00
// the query is so complex because we accidentally stored unique constraint case sensitive
// the query checks first if there is a case sensitive match and afterwards if there is a case insensitive match
deletePlaceholders = append ( deletePlaceholders , fmt . Sprintf ( "(instance_id = $%[1]d AND unique_type = $%[2]d AND unique_field = (SELECT unique_field from (SELECT instance_id, unique_type, unique_field FROM eventstore.unique_constraints WHERE instance_id = $%[1]d AND unique_type = $%[2]d AND unique_field = $%[3]d UNION ALL SELECT instance_id, unique_type, unique_field FROM eventstore.unique_constraints WHERE instance_id = $%[1]d AND unique_type = $%[2]d AND unique_field = LOWER($%[3]d)) LIMIT 1))" , len ( deleteArgs ) + 1 , len ( deleteArgs ) + 2 , len ( deleteArgs ) + 3 ) )
2023-10-19 10:19:10 +00:00
deleteArgs = append ( deleteArgs , command . Aggregate ( ) . InstanceID , constraint . UniqueType , constraint . UniqueField )
2023-10-27 12:10:01 +00:00
deleteConstraints [ fmt . Sprintf ( uniqueConstraintPlaceholderFmt , command . Aggregate ( ) . InstanceID , constraint . UniqueType , constraint . UniqueField ) ] = constraint
2023-10-19 10:19:10 +00:00
case eventstore . UniqueConstraintInstanceRemove :
deletePlaceholders = append ( deletePlaceholders , fmt . Sprintf ( "(instance_id = $%d)" , len ( deleteArgs ) + 1 ) )
deleteArgs = append ( deleteArgs , command . Aggregate ( ) . InstanceID )
2023-10-27 12:10:01 +00:00
deleteConstraints [ fmt . Sprintf ( uniqueConstraintPlaceholderFmt , command . Aggregate ( ) . InstanceID , constraint . UniqueType , constraint . UniqueField ) ] = constraint
2023-10-19 10:19:10 +00:00
}
}
}
if len ( deletePlaceholders ) > 0 {
_ , err := tx . ExecContext ( ctx , fmt . Sprintf ( deleteConstraintStmt , strings . Join ( deletePlaceholders , " OR " ) ) , deleteArgs ... )
if err != nil {
logging . WithError ( err ) . Warn ( "delete unique constraint failed" )
errMessage := "Errors.Internal"
if constraint := constraintFromErr ( err , deleteConstraints ) ; constraint != nil {
errMessage = constraint . ErrorMessage
}
return errs . ThrowInternal ( err , "V3-C8l3V" , errMessage )
}
}
if len ( addPlaceholders ) > 0 {
_ , err := tx . ExecContext ( ctx , fmt . Sprintf ( addConstraintStmt , strings . Join ( addPlaceholders , ", " ) ) , addArgs ... )
if err != nil {
logging . WithError ( err ) . Warn ( "add unique constraint failed" )
errMessage := "Errors.Internal"
if constraint := constraintFromErr ( err , addConstraints ) ; constraint != nil {
errMessage = constraint . ErrorMessage
}
2023-10-27 12:10:01 +00:00
return errs . ThrowAlreadyExists ( err , "V3-DKcYh" , errMessage )
2023-10-19 10:19:10 +00:00
}
}
return nil
}
func constraintFromErr ( err error , constraints map [ string ] * eventstore . UniqueConstraint ) * eventstore . UniqueConstraint {
pgErr := new ( pgconn . PgError )
if ! errors . As ( err , & pgErr ) {
return nil
}
for key , constraint := range constraints {
if strings . Contains ( pgErr . Detail , key ) {
return constraint
}
}
return nil
}