github.com/weaviate/weaviate@v1.24.6/adapters/handlers/rest/handlers_batch_objects.go (about)

     1  //                           _       _
     2  // __      _____  __ ___   ___  __ _| |_ ___
     3  // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
     4  //  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
     5  //   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
     6  //
     7  //  Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
     8  //
     9  //  CONTACT: hello@weaviate.io
    10  //
    11  
    12  package rest
    13  
    14  import (
    15  	"errors"
    16  
    17  	middleware "github.com/go-openapi/runtime/middleware"
    18  	"github.com/go-openapi/strfmt"
    19  	"github.com/sirupsen/logrus"
    20  	"github.com/weaviate/weaviate/adapters/handlers/rest/operations"
    21  	"github.com/weaviate/weaviate/adapters/handlers/rest/operations/batch"
    22  	"github.com/weaviate/weaviate/entities/models"
    23  	"github.com/weaviate/weaviate/entities/verbosity"
    24  	autherrs "github.com/weaviate/weaviate/usecases/auth/authorization/errors"
    25  	"github.com/weaviate/weaviate/usecases/monitoring"
    26  	"github.com/weaviate/weaviate/usecases/objects"
    27  )
    28  
    29  type batchObjectHandlers struct {
    30  	manager             *objects.BatchManager
    31  	metricRequestsTotal restApiRequestsTotal
    32  }
    33  
    34  func (h *batchObjectHandlers) addObjects(params batch.BatchObjectsCreateParams,
    35  	principal *models.Principal,
    36  ) middleware.Responder {
    37  	repl, err := getReplicationProperties(params.ConsistencyLevel, nil)
    38  	if err != nil {
    39  		h.metricRequestsTotal.logError("", err)
    40  		return batch.NewBatchObjectsCreateBadRequest().
    41  			WithPayload(errPayloadFromSingleErr(err))
    42  	}
    43  
    44  	objs, err := h.manager.AddObjects(params.HTTPRequest.Context(), principal,
    45  		params.Body.Objects, params.Body.Fields, repl)
    46  	if err != nil {
    47  		h.metricRequestsTotal.logError("", err)
    48  		switch err.(type) {
    49  		case autherrs.Forbidden:
    50  			return batch.NewBatchObjectsCreateForbidden().
    51  				WithPayload(errPayloadFromSingleErr(err))
    52  		case objects.ErrInvalidUserInput:
    53  			return batch.NewBatchObjectsCreateUnprocessableEntity().
    54  				WithPayload(errPayloadFromSingleErr(err))
    55  		case objects.ErrMultiTenancy:
    56  			return batch.NewBatchObjectsCreateUnprocessableEntity().
    57  				WithPayload(errPayloadFromSingleErr(err))
    58  		default:
    59  			return batch.NewBatchObjectsCreateInternalServerError().
    60  				WithPayload(errPayloadFromSingleErr(err))
    61  		}
    62  	}
    63  
    64  	h.metricRequestsTotal.logOk("")
    65  	return batch.NewBatchObjectsCreateOK().
    66  		WithPayload(h.objectsResponse(objs))
    67  }
    68  
    69  func (h *batchObjectHandlers) objectsResponse(input objects.BatchObjects) []*models.ObjectsGetResponse {
    70  	response := make([]*models.ObjectsGetResponse, len(input))
    71  	for i, object := range input {
    72  		var errorResponse *models.ErrorResponse
    73  		status := models.ObjectsGetResponseAO2ResultStatusSUCCESS
    74  		if object.Err != nil {
    75  			errorResponse = errPayloadFromSingleErr(object.Err)
    76  			status = models.ObjectsGetResponseAO2ResultStatusFAILED
    77  		}
    78  
    79  		object.Object.ID = object.UUID
    80  		response[i] = &models.ObjectsGetResponse{
    81  			Object: *object.Object,
    82  			Result: &models.ObjectsGetResponseAO2Result{
    83  				Errors: errorResponse,
    84  				Status: &status,
    85  			},
    86  		}
    87  	}
    88  
    89  	return response
    90  }
    91  
    92  func (h *batchObjectHandlers) addReferences(params batch.BatchReferencesCreateParams,
    93  	principal *models.Principal,
    94  ) middleware.Responder {
    95  	repl, err := getReplicationProperties(params.ConsistencyLevel, nil)
    96  	if err != nil {
    97  		h.metricRequestsTotal.logError("", err)
    98  		return batch.NewBatchReferencesCreateBadRequest().
    99  			WithPayload(errPayloadFromSingleErr(err))
   100  	}
   101  
   102  	references, err := h.manager.AddReferences(params.HTTPRequest.Context(), principal, params.Body, repl)
   103  	if err != nil {
   104  		h.metricRequestsTotal.logError("", err)
   105  		switch err.(type) {
   106  		case autherrs.Forbidden:
   107  			return batch.NewBatchReferencesCreateForbidden().
   108  				WithPayload(errPayloadFromSingleErr(err))
   109  		case objects.ErrInvalidUserInput:
   110  			return batch.NewBatchReferencesCreateUnprocessableEntity().
   111  				WithPayload(errPayloadFromSingleErr(err))
   112  		case objects.ErrMultiTenancy:
   113  			return batch.NewBatchReferencesCreateUnprocessableEntity().
   114  				WithPayload(errPayloadFromSingleErr(err))
   115  		default:
   116  			return batch.NewBatchReferencesCreateInternalServerError().
   117  				WithPayload(errPayloadFromSingleErr(err))
   118  		}
   119  	}
   120  
   121  	h.metricRequestsTotal.logOk("")
   122  	return batch.NewBatchReferencesCreateOK().
   123  		WithPayload(h.referencesResponse(references))
   124  }
   125  
   126  func (h *batchObjectHandlers) referencesResponse(input objects.BatchReferences) []*models.BatchReferenceResponse {
   127  	response := make([]*models.BatchReferenceResponse, len(input))
   128  	for i, ref := range input {
   129  		var errorResponse *models.ErrorResponse
   130  		var reference models.BatchReference
   131  
   132  		status := models.BatchReferenceResponseAO1ResultStatusSUCCESS
   133  		if ref.Err != nil {
   134  			errorResponse = errPayloadFromSingleErr(ref.Err)
   135  			status = models.BatchReferenceResponseAO1ResultStatusFAILED
   136  		} else {
   137  			reference.From = strfmt.URI(ref.From.String())
   138  			reference.To = strfmt.URI(ref.To.String())
   139  		}
   140  
   141  		response[i] = &models.BatchReferenceResponse{
   142  			BatchReference: reference,
   143  			Result: &models.BatchReferenceResponseAO1Result{
   144  				Errors: errorResponse,
   145  				Status: &status,
   146  			},
   147  		}
   148  	}
   149  
   150  	return response
   151  }
   152  
   153  func (h *batchObjectHandlers) deleteObjects(params batch.BatchObjectsDeleteParams,
   154  	principal *models.Principal,
   155  ) middleware.Responder {
   156  	repl, err := getReplicationProperties(params.ConsistencyLevel, nil)
   157  	if err != nil {
   158  		h.metricRequestsTotal.logError("", err)
   159  		return batch.NewBatchObjectsDeleteBadRequest().
   160  			WithPayload(errPayloadFromSingleErr(err))
   161  	}
   162  
   163  	tenant := getTenant(params.Tenant)
   164  
   165  	res, err := h.manager.DeleteObjects(params.HTTPRequest.Context(), principal,
   166  		params.Body.Match, params.Body.DryRun, params.Body.Output, repl, tenant)
   167  	if err != nil {
   168  		h.metricRequestsTotal.logError("", err)
   169  		if errors.As(err, &objects.ErrInvalidUserInput{}) {
   170  			return batch.NewBatchObjectsDeleteUnprocessableEntity().
   171  				WithPayload(errPayloadFromSingleErr(err))
   172  		} else if errors.As(err, &objects.ErrMultiTenancy{}) {
   173  			return batch.NewBatchObjectsDeleteUnprocessableEntity().
   174  				WithPayload(errPayloadFromSingleErr(err))
   175  		} else if errors.As(err, &autherrs.Forbidden{}) {
   176  			return batch.NewBatchObjectsDeleteForbidden().
   177  				WithPayload(errPayloadFromSingleErr(err))
   178  		} else {
   179  			return batch.NewBatchObjectsDeleteInternalServerError().
   180  				WithPayload(errPayloadFromSingleErr(err))
   181  		}
   182  	}
   183  
   184  	h.metricRequestsTotal.logOk("")
   185  	return batch.NewBatchObjectsDeleteOK().
   186  		WithPayload(h.objectsDeleteResponse(res))
   187  }
   188  
   189  func (h *batchObjectHandlers) objectsDeleteResponse(input *objects.BatchDeleteResponse) *models.BatchDeleteResponse {
   190  	var successful, failed int64
   191  	output := input.Output
   192  	var objects []*models.BatchDeleteResponseResultsObjectsItems0
   193  	for _, obj := range input.Result.Objects {
   194  		var errorResponse *models.ErrorResponse
   195  
   196  		status := models.BatchDeleteResponseResultsObjectsItems0StatusSUCCESS
   197  		if input.DryRun {
   198  			status = models.BatchDeleteResponseResultsObjectsItems0StatusDRYRUN
   199  		} else if obj.Err != nil {
   200  			status = models.BatchDeleteResponseResultsObjectsItems0StatusFAILED
   201  			errorResponse = errPayloadFromSingleErr(obj.Err)
   202  			failed += 1
   203  		} else {
   204  			successful += 1
   205  		}
   206  
   207  		if output == verbosity.OutputMinimal &&
   208  			(status == models.BatchDeleteResponseResultsObjectsItems0StatusSUCCESS ||
   209  				status == models.BatchDeleteResponseResultsObjectsItems0StatusDRYRUN) {
   210  			// only add SUCCESS and DRYRUN results if output is "verbose"
   211  			continue
   212  		}
   213  
   214  		objects = append(objects, &models.BatchDeleteResponseResultsObjectsItems0{
   215  			ID:     obj.UUID,
   216  			Status: &status,
   217  			Errors: errorResponse,
   218  		})
   219  	}
   220  
   221  	response := &models.BatchDeleteResponse{
   222  		Match: &models.BatchDeleteResponseMatch{
   223  			Class: input.Match.Class,
   224  			Where: input.Match.Where,
   225  		},
   226  		DryRun: &input.DryRun,
   227  		Output: &output,
   228  		Results: &models.BatchDeleteResponseResults{
   229  			Matches:    input.Result.Matches,
   230  			Limit:      input.Result.Limit,
   231  			Successful: successful,
   232  			Failed:     failed,
   233  			Objects:    objects,
   234  		},
   235  	}
   236  	return response
   237  }
   238  
   239  func setupObjectBatchHandlers(api *operations.WeaviateAPI, manager *objects.BatchManager, metrics *monitoring.PrometheusMetrics, logger logrus.FieldLogger) {
   240  	h := &batchObjectHandlers{manager, newBatchRequestsTotal(metrics, logger)}
   241  
   242  	api.BatchBatchObjectsCreateHandler = batch.
   243  		BatchObjectsCreateHandlerFunc(h.addObjects)
   244  	api.BatchBatchReferencesCreateHandler = batch.
   245  		BatchReferencesCreateHandlerFunc(h.addReferences)
   246  	api.BatchBatchObjectsDeleteHandler = batch.
   247  		BatchObjectsDeleteHandlerFunc(h.deleteObjects)
   248  }
   249  
   250  type batchRequestsTotal struct {
   251  	*restApiRequestsTotalImpl
   252  }
   253  
   254  func newBatchRequestsTotal(metrics *monitoring.PrometheusMetrics, logger logrus.FieldLogger) restApiRequestsTotal {
   255  	return &batchRequestsTotal{
   256  		restApiRequestsTotalImpl: &restApiRequestsTotalImpl{newRequestsTotalMetric(metrics, "rest"), "rest", "batch", logger},
   257  	}
   258  }
   259  
   260  func (e *batchRequestsTotal) logError(className string, err error) {
   261  	switch err.(type) {
   262  	case errReplication:
   263  		e.logUserError(className)
   264  	case autherrs.Forbidden, objects.ErrInvalidUserInput:
   265  		e.logUserError(className)
   266  	case objects.ErrMultiTenancy:
   267  		e.logUserError(className)
   268  	default:
   269  		if errors.As(err, &objects.ErrMultiTenancy{}) ||
   270  			errors.As(err, &objects.ErrInvalidUserInput{}) ||
   271  			errors.As(err, &autherrs.Forbidden{}) {
   272  			e.logUserError(className)
   273  		} else {
   274  			e.logServerError(className, err)
   275  		}
   276  	}
   277  }