3bdc27f4de
Continue finishing the parts of the text extraction plan * add missing fields
198 lines
6.5 KiB
Go
198 lines
6.5 KiB
Go
package fieldextraction
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"queryorchestration/internal/database/repository"
|
|
"queryorchestration/internal/serviceconfig"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
// Service provides field extraction operations
|
|
type Service struct {
|
|
cfg serviceconfig.ConfigProvider
|
|
}
|
|
|
|
// New creates a new field extraction service
|
|
func New(cfg serviceconfig.ConfigProvider) *Service {
|
|
return &Service{cfg: cfg}
|
|
}
|
|
|
|
// CreateFieldExtraction creates a new field extraction with all single and array fields
|
|
// in a single transaction. All array fields must have consistent length.
|
|
// Returns the created field extraction record.
|
|
func (s *Service) CreateFieldExtraction(ctx context.Context, input *CreateFieldExtractionInput) (*repository.Documentfieldextraction, error) {
|
|
if input == nil {
|
|
return nil, fmt.Errorf("input cannot be nil")
|
|
}
|
|
|
|
if input.DocumentID == uuid.Nil {
|
|
return nil, fmt.Errorf("documentID cannot be nil")
|
|
}
|
|
|
|
if input.SingleFields == nil {
|
|
return nil, fmt.Errorf("singleFields cannot be nil")
|
|
}
|
|
|
|
if input.SingleFields.Createdby == "" {
|
|
return nil, fmt.Errorf("createdBy cannot be empty")
|
|
}
|
|
|
|
// Validate array consistency
|
|
_, err := ValidateArrayConsistency(input.ArrayFields)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("array validation failed: %w", err)
|
|
}
|
|
|
|
// Begin transaction
|
|
tx, err := s.cfg.GetDBPool().Begin(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to begin transaction: %w", err)
|
|
}
|
|
defer func() {
|
|
_ = tx.Rollback(ctx)
|
|
}()
|
|
|
|
queries := s.cfg.GetDBQueries().WithTx(tx)
|
|
|
|
// Set document ID in single fields
|
|
input.SingleFields.Documentid = input.DocumentID
|
|
|
|
// Create main field extraction record with single fields
|
|
fieldExtraction, err := queries.AddFieldExtraction(ctx, input.SingleFields)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create field extraction: %w", err)
|
|
}
|
|
|
|
// Lock existing version rows for this document to prevent concurrent version creation
|
|
// This ensures that two concurrent requests cannot create the same version number
|
|
_, err = queries.LockFieldExtractionVersionsForDocument(ctx, input.DocumentID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to lock version rows: %w", err)
|
|
}
|
|
|
|
// Get the max version number for this document (after locking)
|
|
// COALESCE in the query ensures we get 0 if no versions exist
|
|
maxVersionPtr, err := queries.GetMaxFieldExtractionVersion(ctx, input.DocumentID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get max version: %w", err)
|
|
}
|
|
// SQLC returns *int64 even though COALESCE guarantees non-NULL
|
|
// Dereference safely - if somehow nil, treat as 0
|
|
var maxVersion int64
|
|
if maxVersionPtr != nil {
|
|
maxVersion = *maxVersionPtr
|
|
}
|
|
// Next version is always maxVersion + 1 (1 for first version since maxVersion is 0)
|
|
version := maxVersion + 1
|
|
|
|
// Create version entry with documentId for the unique constraint
|
|
err = queries.AddFieldExtractionEntry(ctx, &repository.AddFieldExtractionEntryParams{
|
|
Fieldextractionid: fieldExtraction.ID,
|
|
Documentid: input.DocumentID,
|
|
Version: version,
|
|
Createdby: input.SingleFields.Createdby,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create field extraction version entry: %w", err)
|
|
}
|
|
|
|
// Insert all array field rows
|
|
for arrayIndex, arrayField := range input.ArrayFields {
|
|
// Set field extraction ID and array index
|
|
arrayField.Fieldextractionid = fieldExtraction.ID
|
|
//nolint:gosec // Array index is expected to be small, int16 is sufficient
|
|
arrayField.Arrayindex = int16(arrayIndex)
|
|
|
|
err = queries.AddFieldExtractionArrayField(ctx, arrayField)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create array field at index %d: %w", arrayIndex, err)
|
|
}
|
|
}
|
|
|
|
// Commit transaction
|
|
if err := tx.Commit(ctx); err != nil {
|
|
return nil, fmt.Errorf("failed to commit transaction: %w", err)
|
|
}
|
|
|
|
return fieldExtraction, nil
|
|
}
|
|
|
|
// GetCurrentFieldExtraction retrieves the most recent field extraction for a document
|
|
// Returns nil if no field extraction exists for the document
|
|
func (s *Service) GetCurrentFieldExtraction(ctx context.Context, documentID uuid.UUID) (*repository.Currentfieldextraction, error) {
|
|
if documentID == uuid.Nil {
|
|
return nil, fmt.Errorf("documentID cannot be nil")
|
|
}
|
|
|
|
fieldExtraction, err := s.cfg.GetDBQueries().GetCurrentFieldExtraction(ctx, documentID)
|
|
if err != nil {
|
|
if err == pgx.ErrNoRows {
|
|
return nil, nil
|
|
}
|
|
return nil, fmt.Errorf("failed to get current field extraction: %w", err)
|
|
}
|
|
|
|
return fieldExtraction, nil
|
|
}
|
|
|
|
// GetFieldExtractionHistory retrieves all field extraction versions for a document
|
|
// ordered by version (most recent first)
|
|
func (s *Service) GetFieldExtractionHistory(ctx context.Context, documentID uuid.UUID) ([]*repository.GetFieldExtractionHistoryRow, error) {
|
|
if documentID == uuid.Nil {
|
|
return nil, fmt.Errorf("documentID cannot be nil")
|
|
}
|
|
|
|
history, err := s.cfg.GetDBQueries().GetFieldExtractionHistory(ctx, documentID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get field extraction history: %w", err)
|
|
}
|
|
|
|
return history, nil
|
|
}
|
|
|
|
// GetFieldExtractionArrayFields retrieves all array fields for a specific field extraction
|
|
// ordered by arrayIndex
|
|
func (s *Service) GetFieldExtractionArrayFields(ctx context.Context, fieldExtractionID uuid.UUID) ([]*repository.Documentfieldextractionarrayfield, error) {
|
|
if fieldExtractionID == uuid.Nil {
|
|
return nil, fmt.Errorf("fieldExtractionID cannot be nil")
|
|
}
|
|
|
|
arrayFields, err := s.cfg.GetDBQueries().GetFieldExtractionArrayFields(ctx, fieldExtractionID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get array fields: %w", err)
|
|
}
|
|
|
|
return arrayFields, nil
|
|
}
|
|
|
|
// GetFieldExtractionByVersion retrieves a specific version of field extraction for a document
|
|
// Returns nil if no field extraction exists for the document at that version
|
|
// documentID: the UUID of the document
|
|
// version: the version number to retrieve (must be >= 1)
|
|
func (s *Service) GetFieldExtractionByVersion(ctx context.Context, documentID uuid.UUID, version int64) (*repository.GetFieldExtractionByVersionRow, error) {
|
|
if documentID == uuid.Nil {
|
|
return nil, fmt.Errorf("documentID cannot be nil")
|
|
}
|
|
|
|
if version < 1 {
|
|
return nil, fmt.Errorf("version must be at least 1")
|
|
}
|
|
|
|
fieldExtraction, err := s.cfg.GetDBQueries().GetFieldExtractionByVersion(ctx, &repository.GetFieldExtractionByVersionParams{
|
|
Documentid: documentID,
|
|
Version: version,
|
|
})
|
|
if err != nil {
|
|
if err == pgx.ErrNoRows {
|
|
return nil, nil
|
|
}
|
|
return nil, fmt.Errorf("failed to get field extraction by version: %w", err)
|
|
}
|
|
|
|
return fieldExtraction, nil
|
|
}
|