package themis import ( "context" "fmt" "github.com/rs/zerolog/log" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" semconv "go.opentelemetry.io/otel/semconv/v1.24.0" "go.opentelemetry.io/otel/trace" ) type Conflict struct { Province string Player string ClaimType ClaimType Claim string ClaimID int } func (c Conflict) String() string { return fmt.Sprintf("%s owned by #%d %s %s (%s)", c.Province, c.ClaimID, c.ClaimType, c.Claim, c.Player) } const conflictQuery string = `WITH claiming AS ( SELECT province FROM claimables WHERE claimables.typ = ? AND claimables.name = ? ) SELECT claimables.province, claims.player, claims.claim_type, claims.val, claims.id FROM claims INNER JOIN claimables ON claims.claim_type = claimables.typ AND claims.val = claimables.name INNER JOIN claiming ON claiming.province = claimables.province WHERE claims.userid IS NOT ?;` func (s *Store) FindConflicts(ctx context.Context, userId, name string, claimType ClaimType) ([]Conflict, error) { ctx, span := tracer.Start(ctx, "find_conflicts", trace.WithAttributes( semconv.DBSystemSqlite, semconv.DBSQLTable("claims"), semconv.DBOperation("select"), attribute.String("user_id", userId), attribute.String("claim_name", name), attribute.Stringer("claim_type", claimType), )) defer span.End() log.Debug().Ctx(ctx).Stringer("claim_type", claimType).Str("userid", userId).Msg("searching for potential conflicts") stmt, err := s.db.PrepareContext(ctx, conflictQuery) if err != nil { span.RecordError(err) span.SetStatus(codes.Error, "failed to prepare query") return nil, fmt.Errorf("failed to prepare conflicts query: %w", err) } rows, err := stmt.QueryContext(ctx, claimType, name, userId) if err != nil { span.RecordError(err) span.SetStatus(codes.Error, "failed to execute query") return nil, fmt.Errorf("failed to get conflicting provinces: %w", err) } defer stmt.Close() conflicts := make([]Conflict, 0) for rows.Next() { var ( province string player string sClaimType string claimName string claimId int ) err = rows.Scan(&province, &player, &sClaimType, &claimName, &claimId) if err != nil { span.RecordError(err) span.SetStatus(codes.Error, "failed to scan row") return nil, fmt.Errorf("failed to scan row: %w", err) } ct, err := ClaimTypeFromString(sClaimType) if err != nil { // In case of an error parsing the claim type, simply default to // whatever the database sends; this is a read-only function, the // input validation is assumed to have already been done at insert. ct = ClaimType(sClaimType) } conflicts = append(conflicts, Conflict{ Province: province, Player: player, ClaimType: ct, Claim: claimName, ClaimID: claimId, }) } return conflicts, nil }