133 lines
3.6 KiB
Go
133 lines
3.6 KiB
Go
package idgen
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
)
|
|
|
|
// EntityType represents the type of entity for ID generation
|
|
type EntityType string
|
|
|
|
const (
|
|
Customer EntityType = "customer"
|
|
Project EntityType = "project"
|
|
Location EntityType = "location"
|
|
Lot EntityType = "lot"
|
|
Asset EntityType = "machine" // Renamed from asset
|
|
Component EntityType = "part" // Renamed from component
|
|
Installation EntityType = "installation"
|
|
LogBundle EntityType = "log_bundle"
|
|
Observation EntityType = "observation"
|
|
TimelineEvent EntityType = "timeline_event"
|
|
Ticket EntityType = "ticket"
|
|
TicketLink EntityType = "ticket_link"
|
|
FailureEvent EntityType = "failure_event"
|
|
LotModelMapping EntityType = "lot_model_mapping"
|
|
)
|
|
|
|
// Prefix mapping for each entity type
|
|
var prefixMap = map[EntityType]string{
|
|
Customer: "CR",
|
|
Project: "PJ",
|
|
Location: "LN",
|
|
Lot: "LT",
|
|
Asset: "ME", // Machine
|
|
Component: "PT", // Part
|
|
Installation: "IN",
|
|
LogBundle: "LB",
|
|
Observation: "OB",
|
|
TimelineEvent: "TE",
|
|
Ticket: "TT",
|
|
TicketLink: "TL",
|
|
FailureEvent: "FE",
|
|
LotModelMapping: "LM",
|
|
}
|
|
|
|
// Generator handles unique ID generation with prefixes
|
|
type Generator struct {
|
|
db *sql.DB
|
|
}
|
|
|
|
// NewGenerator creates a new ID generator
|
|
func NewGenerator(db *sql.DB) *Generator {
|
|
return &Generator{db: db}
|
|
}
|
|
|
|
// Generate creates a new unique ID for the given entity type
|
|
// Format: PREFIX-NNNNNNN (e.g., CR-0000001)
|
|
// This uses row-level locking to ensure thread-safe sequence generation
|
|
func (g *Generator) Generate(ctx context.Context, entityType EntityType) (string, error) {
|
|
prefix, ok := prefixMap[entityType]
|
|
if !ok {
|
|
return "", fmt.Errorf("unknown entity type: %s", entityType)
|
|
}
|
|
|
|
// Start transaction for atomic sequence increment
|
|
tx, err := g.db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to begin transaction: %w", err)
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
// Lock the row and get current sequence value
|
|
var nextValue int64
|
|
err = tx.QueryRowContext(ctx,
|
|
`SELECT next_value FROM id_sequences WHERE entity_type = ? FOR UPDATE`,
|
|
string(entityType),
|
|
).Scan(&nextValue)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return "", fmt.Errorf("sequence not found for entity type: %s", entityType)
|
|
}
|
|
return "", fmt.Errorf("failed to get sequence: %w", err)
|
|
}
|
|
|
|
// Increment the sequence
|
|
_, err = tx.ExecContext(ctx,
|
|
`UPDATE id_sequences SET next_value = next_value + 1 WHERE entity_type = ?`,
|
|
string(entityType),
|
|
)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to update sequence: %w", err)
|
|
}
|
|
|
|
// Commit transaction
|
|
if err := tx.Commit(); err != nil {
|
|
return "", fmt.Errorf("failed to commit transaction: %w", err)
|
|
}
|
|
|
|
// Format the ID with prefix and zero-padded number
|
|
return FormatID(prefix, nextValue), nil
|
|
}
|
|
|
|
// FormatID formats a numeric ID with the given prefix
|
|
// Example: FormatID("CR", 1) returns "CR-0000001"
|
|
func FormatID(prefix string, number int64) string {
|
|
return fmt.Sprintf("%s-%07d", prefix, number)
|
|
}
|
|
|
|
// ParseID extracts the numeric part from a formatted ID
|
|
// Example: ParseID("CR-0000001") returns 1, nil
|
|
func ParseID(id string) (int64, error) {
|
|
var number int64
|
|
var prefix string
|
|
|
|
// Try to parse the formatted ID
|
|
_, err := fmt.Sscanf(id, "%2s-%07d", &prefix, &number)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("invalid ID format: %s", id)
|
|
}
|
|
|
|
return number, nil
|
|
}
|
|
|
|
// GetPrefix returns the prefix for a given entity type
|
|
func GetPrefix(entityType EntityType) (string, error) {
|
|
prefix, ok := prefixMap[entityType]
|
|
if !ok {
|
|
return "", fmt.Errorf("unknown entity type: %s", entityType)
|
|
}
|
|
return prefix, nil
|
|
}
|