github.com/weaviate/weaviate@v1.24.6/adapters/handlers/rest/clusterapi/indices.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 clusterapi
    13  
    14  import (
    15  	"context"
    16  	"encoding/base64"
    17  	"encoding/json"
    18  	"fmt"
    19  	"io"
    20  	"net/http"
    21  	"regexp"
    22  
    23  	"github.com/go-openapi/strfmt"
    24  	"github.com/pkg/errors"
    25  	"github.com/weaviate/weaviate/entities/additional"
    26  	"github.com/weaviate/weaviate/entities/aggregation"
    27  	"github.com/weaviate/weaviate/entities/filters"
    28  	entschema "github.com/weaviate/weaviate/entities/schema"
    29  	"github.com/weaviate/weaviate/entities/search"
    30  	"github.com/weaviate/weaviate/entities/searchparams"
    31  	"github.com/weaviate/weaviate/entities/storobj"
    32  	"github.com/weaviate/weaviate/usecases/objects"
    33  	"github.com/weaviate/weaviate/usecases/replica"
    34  )
    35  
    36  type indices struct {
    37  	shards                 shards
    38  	db                     db
    39  	auth                   auth
    40  	regexpObjects          *regexp.Regexp
    41  	regexpObjectsOverwrite *regexp.Regexp
    42  	regexObjectsDigest     *regexp.Regexp
    43  	regexpObjectsSearch    *regexp.Regexp
    44  	regexpObjectsFind      *regexp.Regexp
    45  
    46  	regexpObjectsAggregations *regexp.Regexp
    47  	regexpObject              *regexp.Regexp
    48  	regexpReferences          *regexp.Regexp
    49  	regexpShardsQueueSize     *regexp.Regexp
    50  	regexpShardsStatus        *regexp.Regexp
    51  	regexpShardFiles          *regexp.Regexp
    52  	regexpShard               *regexp.Regexp
    53  	regexpShardReinit         *regexp.Regexp
    54  }
    55  
    56  const (
    57  	cl = entschema.ClassNameRegexCore
    58  	sh = entschema.ShardNameRegexCore
    59  	ob = `[A-Za-z0-9_+-]+`
    60  
    61  	urlPatternObjects = `\/indices\/(` + cl + `)` +
    62  		`\/shards\/(` + sh + `)\/objects`
    63  	urlPatternObjectsOverwrite = `\/indices\/(` + cl + `)` +
    64  		`\/shards\/(` + sh + `)\/objects:overwrite`
    65  	urlPatternObjectsDigest = `\/indices\/(` + cl + `)` +
    66  		`\/shards\/(` + sh + `)\/objects:digest`
    67  	urlPatternObjectsSearch = `\/indices\/(` + cl + `)` +
    68  		`\/shards\/(` + sh + `)\/objects\/_search`
    69  	urlPatternObjectsFind = `\/indices\/(` + cl + `)` +
    70  		`\/shards\/(` + sh + `)\/objects\/_find`
    71  	urlPatternObjectsAggregations = `\/indices\/(` + cl + `)` +
    72  		`\/shards\/(` + sh + `)\/objects\/_aggregations`
    73  	urlPatternObject = `\/indices\/(` + cl + `)` +
    74  		`\/shards\/(` + sh + `)\/objects\/(` + ob + `)`
    75  	urlPatternReferences = `\/indices\/(` + cl + `)` +
    76  		`\/shards\/(` + sh + `)\/references`
    77  	urlPatternShardsQueueSize = `\/indices\/(` + cl + `)` +
    78  		`\/shards\/(` + sh + `)\/queuesize`
    79  	urlPatternShardsStatus = `\/indices\/(` + cl + `)` +
    80  		`\/shards\/(` + sh + `)\/status`
    81  	urlPatternShardFiles = `\/indices\/(` + cl + `)` +
    82  		`\/shards\/(` + sh + `)\/files/(.*)`
    83  	urlPatternShard = `\/indices\/(` + cl + `)` +
    84  		`\/shards\/(` + sh + `)$`
    85  	urlPatternShardReinit = `\/indices\/(` + cl + `)` +
    86  		`\/shards\/(` + sh + `):reinit`
    87  )
    88  
    89  type shards interface {
    90  	PutObject(ctx context.Context, indexName, shardName string,
    91  		obj *storobj.Object) error
    92  	BatchPutObjects(ctx context.Context, indexName, shardName string,
    93  		objs []*storobj.Object) []error
    94  	BatchAddReferences(ctx context.Context, indexName, shardName string,
    95  		refs objects.BatchReferences) []error
    96  	GetObject(ctx context.Context, indexName, shardName string,
    97  		id strfmt.UUID, selectProperties search.SelectProperties,
    98  		additional additional.Properties) (*storobj.Object, error)
    99  	Exists(ctx context.Context, indexName, shardName string,
   100  		id strfmt.UUID) (bool, error)
   101  	DeleteObject(ctx context.Context, indexName, shardName string,
   102  		id strfmt.UUID) error
   103  	MergeObject(ctx context.Context, indexName, shardName string,
   104  		mergeDoc objects.MergeDocument) error
   105  	MultiGetObjects(ctx context.Context, indexName, shardName string,
   106  		id []strfmt.UUID) ([]*storobj.Object, error)
   107  	Search(ctx context.Context, indexName, shardName string,
   108  		vector []float32, targetVector string, distance float32, limit int,
   109  		filters *filters.LocalFilter, keywordRanking *searchparams.KeywordRanking,
   110  		sort []filters.Sort, cursor *filters.Cursor, groupBy *searchparams.GroupBy,
   111  		additional additional.Properties,
   112  	) ([]*storobj.Object, []float32, error)
   113  	Aggregate(ctx context.Context, indexName, shardName string,
   114  		params aggregation.Params) (*aggregation.Result, error)
   115  	FindUUIDs(ctx context.Context, indexName, shardName string,
   116  		filters *filters.LocalFilter) ([]strfmt.UUID, error)
   117  	DeleteObjectBatch(ctx context.Context, indexName, shardName string,
   118  		uuids []strfmt.UUID, dryRun bool) objects.BatchSimpleObjects
   119  	GetShardQueueSize(ctx context.Context, indexName, shardName string) (int64, error)
   120  	GetShardStatus(ctx context.Context, indexName, shardName string) (string, error)
   121  	UpdateShardStatus(ctx context.Context, indexName, shardName,
   122  		targetStatus string) error
   123  
   124  	// Replication-specific
   125  	OverwriteObjects(ctx context.Context, indexName, shardName string,
   126  		vobjects []*objects.VObject) ([]replica.RepairResponse, error)
   127  	DigestObjects(ctx context.Context, indexName, shardName string,
   128  		ids []strfmt.UUID) (result []replica.RepairResponse, err error)
   129  
   130  	// Scale-out Replication POC
   131  	FilePutter(ctx context.Context, indexName, shardName,
   132  		filePath string) (io.WriteCloser, error)
   133  	CreateShard(ctx context.Context, indexName, shardName string) error
   134  	ReInitShard(ctx context.Context, indexName, shardName string) error
   135  }
   136  
   137  type db interface {
   138  	StartupComplete() bool
   139  }
   140  
   141  func NewIndices(shards shards, db db, auth auth) *indices {
   142  	return &indices{
   143  		regexpObjects:          regexp.MustCompile(urlPatternObjects),
   144  		regexpObjectsOverwrite: regexp.MustCompile(urlPatternObjectsOverwrite),
   145  		regexObjectsDigest:     regexp.MustCompile(urlPatternObjectsDigest),
   146  		regexpObjectsSearch:    regexp.MustCompile(urlPatternObjectsSearch),
   147  		regexpObjectsFind:      regexp.MustCompile(urlPatternObjectsFind),
   148  
   149  		regexpObjectsAggregations: regexp.MustCompile(urlPatternObjectsAggregations),
   150  		regexpObject:              regexp.MustCompile(urlPatternObject),
   151  		regexpReferences:          regexp.MustCompile(urlPatternReferences),
   152  		regexpShardsQueueSize:     regexp.MustCompile(urlPatternShardsQueueSize),
   153  		regexpShardsStatus:        regexp.MustCompile(urlPatternShardsStatus),
   154  		regexpShardFiles:          regexp.MustCompile(urlPatternShardFiles),
   155  		regexpShard:               regexp.MustCompile(urlPatternShard),
   156  		regexpShardReinit:         regexp.MustCompile(urlPatternShardReinit),
   157  		shards:                    shards,
   158  		db:                        db,
   159  		auth:                      auth,
   160  	}
   161  }
   162  
   163  func (i *indices) Indices() http.Handler {
   164  	return i.auth.handleFunc(i.indicesHandler())
   165  }
   166  
   167  func (i *indices) indicesHandler() http.HandlerFunc {
   168  	return func(w http.ResponseWriter, r *http.Request) {
   169  		path := r.URL.Path
   170  		switch {
   171  		case i.regexpObjectsSearch.MatchString(path):
   172  			if r.Method != http.MethodPost {
   173  				http.Error(w, "405 Method not Allowed", http.StatusMethodNotAllowed)
   174  				return
   175  			}
   176  
   177  			i.postSearchObjects().ServeHTTP(w, r)
   178  			return
   179  		case i.regexpObjectsFind.MatchString(path):
   180  			if r.Method != http.MethodPost {
   181  				http.Error(w, "405 Method not Allowed", http.StatusMethodNotAllowed)
   182  				return
   183  			}
   184  
   185  			i.postFindUUIDs().ServeHTTP(w, r)
   186  			return
   187  		case i.regexpObjectsAggregations.MatchString(path):
   188  			if r.Method != http.MethodPost {
   189  				http.Error(w, "405 Method not Allowed", http.StatusMethodNotAllowed)
   190  				return
   191  			}
   192  
   193  			i.postAggregateObjects().ServeHTTP(w, r)
   194  			return
   195  		case i.regexpObjectsOverwrite.MatchString(path):
   196  			if r.Method != http.MethodPut {
   197  				http.Error(w, "405 Method not Allowed", http.StatusMethodNotAllowed)
   198  			}
   199  
   200  			i.putOverwriteObjects().ServeHTTP(w, r)
   201  		case i.regexObjectsDigest.MatchString(path):
   202  			if r.Method != http.MethodGet {
   203  				http.Error(w, "405 Method not Allowed", http.StatusMethodNotAllowed)
   204  			}
   205  
   206  			i.getObjectsDigest().ServeHTTP(w, r)
   207  		case i.regexpObject.MatchString(path):
   208  			if r.Method == http.MethodGet {
   209  				i.getObject().ServeHTTP(w, r)
   210  				return
   211  			}
   212  			if r.Method == http.MethodDelete {
   213  				i.deleteObject().ServeHTTP(w, r)
   214  				return
   215  			}
   216  			if r.Method == http.MethodPatch {
   217  				i.mergeObject().ServeHTTP(w, r)
   218  				return
   219  			}
   220  
   221  			http.Error(w, "405 Method not Allowed", http.StatusMethodNotAllowed)
   222  			return
   223  
   224  		case i.regexpObjects.MatchString(path):
   225  			if r.Method == http.MethodGet {
   226  				i.getObjectsMulti().ServeHTTP(w, r)
   227  				return
   228  			}
   229  			if r.Method == http.MethodPost {
   230  				i.postObject().ServeHTTP(w, r)
   231  				return
   232  			}
   233  			if r.Method == http.MethodDelete {
   234  				i.deleteObjects().ServeHTTP(w, r)
   235  				return
   236  			}
   237  			http.Error(w, "405 Method not Allowed", http.StatusMethodNotAllowed)
   238  			return
   239  
   240  		case i.regexpReferences.MatchString(path):
   241  			if r.Method != http.MethodPost {
   242  				http.Error(w, "405 Method not Allowed", http.StatusMethodNotAllowed)
   243  				return
   244  			}
   245  
   246  			i.postReferences().ServeHTTP(w, r)
   247  			return
   248  		case i.regexpShardsQueueSize.MatchString(path):
   249  			if r.Method == http.MethodGet {
   250  				i.getGetShardQueueSize().ServeHTTP(w, r)
   251  				return
   252  			}
   253  			http.Error(w, "405 Method not Allowed", http.StatusMethodNotAllowed)
   254  			return
   255  		case i.regexpShardsStatus.MatchString(path):
   256  			if r.Method == http.MethodGet {
   257  				i.getGetShardStatus().ServeHTTP(w, r)
   258  				return
   259  			}
   260  			if r.Method == http.MethodPost {
   261  				i.postUpdateShardStatus().ServeHTTP(w, r)
   262  				return
   263  			}
   264  			http.Error(w, "405 Method not Allowed", http.StatusMethodNotAllowed)
   265  			return
   266  
   267  		case i.regexpShardFiles.MatchString(path):
   268  			if r.Method == http.MethodPost {
   269  				i.postShardFile().ServeHTTP(w, r)
   270  				return
   271  			}
   272  			http.Error(w, "405 Method not Allowed", http.StatusMethodNotAllowed)
   273  			return
   274  
   275  		case i.regexpShard.MatchString(path):
   276  			if r.Method == http.MethodPost {
   277  				i.postShard().ServeHTTP(w, r)
   278  				return
   279  			}
   280  			http.Error(w, "405 Method not Allowed", http.StatusMethodNotAllowed)
   281  			return
   282  		case i.regexpShardReinit.MatchString(path):
   283  			if r.Method == http.MethodPut {
   284  				i.putShardReinit().ServeHTTP(w, r)
   285  				return
   286  			}
   287  			http.Error(w, "405 Method not Allowed", http.StatusMethodNotAllowed)
   288  			return
   289  
   290  		default:
   291  			http.NotFound(w, r)
   292  			return
   293  		}
   294  	}
   295  }
   296  
   297  func (i *indices) postObject() http.Handler {
   298  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   299  		args := i.regexpObjects.FindStringSubmatch(r.URL.Path)
   300  		if len(args) != 3 {
   301  			http.Error(w, "invalid URI", http.StatusBadRequest)
   302  			return
   303  		}
   304  
   305  		index, shard := args[1], args[2]
   306  
   307  		defer r.Body.Close()
   308  
   309  		ct := r.Header.Get("content-type")
   310  
   311  		switch ct {
   312  		case IndicesPayloads.ObjectList.MIME():
   313  			i.postObjectBatch(w, r, index, shard)
   314  			return
   315  
   316  		case IndicesPayloads.SingleObject.MIME():
   317  			i.postObjectSingle(w, r, index, shard)
   318  			return
   319  
   320  		default:
   321  			http.Error(w, "415 Unsupported Media Type", http.StatusUnsupportedMediaType)
   322  			return
   323  		}
   324  	})
   325  }
   326  
   327  func (i *indices) postObjectSingle(w http.ResponseWriter, r *http.Request,
   328  	index, shard string,
   329  ) {
   330  	bodyBytes, err := io.ReadAll(r.Body)
   331  	if err != nil {
   332  		http.Error(w, err.Error(), http.StatusInternalServerError)
   333  		return
   334  	}
   335  
   336  	obj, err := IndicesPayloads.SingleObject.Unmarshal(bodyBytes)
   337  	if err != nil {
   338  		http.Error(w, err.Error(), http.StatusInternalServerError)
   339  		return
   340  	}
   341  
   342  	if err := i.shards.PutObject(r.Context(), index, shard, obj); err != nil {
   343  		http.Error(w, err.Error(), http.StatusInternalServerError)
   344  		return
   345  	}
   346  
   347  	w.WriteHeader(http.StatusNoContent)
   348  }
   349  
   350  func (i *indices) postObjectBatch(w http.ResponseWriter, r *http.Request,
   351  	index, shard string,
   352  ) {
   353  	bodyBytes, err := io.ReadAll(r.Body)
   354  	if err != nil {
   355  		http.Error(w, err.Error(), http.StatusInternalServerError)
   356  		return
   357  	}
   358  
   359  	objs, err := IndicesPayloads.ObjectList.Unmarshal(bodyBytes)
   360  	if err != nil {
   361  		http.Error(w, err.Error(), http.StatusInternalServerError)
   362  		return
   363  	}
   364  
   365  	errs := i.shards.BatchPutObjects(r.Context(), index, shard, objs)
   366  	errsJSON, err := IndicesPayloads.ErrorList.Marshal(errs)
   367  	if err != nil {
   368  		http.Error(w, err.Error(), http.StatusInternalServerError)
   369  		return
   370  	}
   371  
   372  	IndicesPayloads.ErrorList.SetContentTypeHeader(w)
   373  	w.Write(errsJSON)
   374  }
   375  
   376  func (i *indices) getObject() http.Handler {
   377  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   378  		args := i.regexpObject.FindStringSubmatch(r.URL.Path)
   379  		if len(args) != 4 {
   380  			http.Error(w, "invalid URI", http.StatusBadRequest)
   381  			return
   382  		}
   383  
   384  		index, shard, id := args[1], args[2], args[3]
   385  
   386  		defer r.Body.Close()
   387  
   388  		if r.URL.Query().Get("check_exists") != "" {
   389  			i.checkExists(w, r, index, shard, id)
   390  			return
   391  		}
   392  
   393  		additionalEncoded := r.URL.Query().Get("additional")
   394  		if additionalEncoded == "" {
   395  			http.Error(w, "missing required url param 'additional'",
   396  				http.StatusBadRequest)
   397  			return
   398  		}
   399  
   400  		additionalBytes, err := base64.StdEncoding.DecodeString(additionalEncoded)
   401  		if err != nil {
   402  			http.Error(w, "base64 decode 'additional' param: "+err.Error(),
   403  				http.StatusBadRequest)
   404  			return
   405  		}
   406  
   407  		selectPropertiesEncoded := r.URL.Query().Get("selectProperties")
   408  		if selectPropertiesEncoded == "" {
   409  			http.Error(w, "missing required url param 'selectProperties'",
   410  				http.StatusBadRequest)
   411  			return
   412  		}
   413  
   414  		selectPropertiesBytes, err := base64.StdEncoding.
   415  			DecodeString(selectPropertiesEncoded)
   416  		if err != nil {
   417  			http.Error(w, "base64 decode 'selectProperties' param: "+err.Error(),
   418  				http.StatusBadRequest)
   419  			return
   420  		}
   421  
   422  		var additional additional.Properties
   423  		if err := json.Unmarshal(additionalBytes, &additional); err != nil {
   424  			http.Error(w, "unmarshal 'additional' param from json: "+err.Error(),
   425  				http.StatusBadRequest)
   426  			return
   427  		}
   428  
   429  		var selectProperties search.SelectProperties
   430  		if err := json.Unmarshal(selectPropertiesBytes, &selectProperties); err != nil {
   431  			http.Error(w, "unmarshal 'selectProperties' param from json: "+err.Error(),
   432  				http.StatusBadRequest)
   433  			return
   434  		}
   435  		if !i.db.StartupComplete() {
   436  			http.Error(w, "startup is not complete", http.StatusServiceUnavailable)
   437  			return
   438  		}
   439  		obj, err := i.shards.GetObject(r.Context(), index, shard, strfmt.UUID(id),
   440  			selectProperties, additional)
   441  		if err != nil {
   442  			http.Error(w, err.Error(), http.StatusInternalServerError)
   443  		}
   444  
   445  		if obj == nil {
   446  			// this is a legitimate case - the requested ID doesn't exist, don't try
   447  			// to marshal anything
   448  			w.WriteHeader(http.StatusNotFound)
   449  			return
   450  		}
   451  
   452  		objBytes, err := IndicesPayloads.SingleObject.Marshal(obj)
   453  		if err != nil {
   454  			http.Error(w, err.Error(), http.StatusInternalServerError)
   455  		}
   456  
   457  		IndicesPayloads.SingleObject.SetContentTypeHeader(w)
   458  		w.Write(objBytes)
   459  	})
   460  }
   461  
   462  func (i *indices) checkExists(w http.ResponseWriter, r *http.Request,
   463  	index, shard, id string,
   464  ) {
   465  	ok, err := i.shards.Exists(r.Context(), index, shard, strfmt.UUID(id))
   466  	if err != nil {
   467  		http.Error(w, err.Error(), http.StatusInternalServerError)
   468  	}
   469  
   470  	if ok {
   471  		w.WriteHeader(http.StatusNoContent)
   472  	} else {
   473  		w.WriteHeader(http.StatusNotFound)
   474  	}
   475  }
   476  
   477  func (i *indices) deleteObject() http.Handler {
   478  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   479  		args := i.regexpObject.FindStringSubmatch(r.URL.Path)
   480  		if len(args) != 4 {
   481  			http.Error(w, "invalid URI", http.StatusBadRequest)
   482  			return
   483  		}
   484  
   485  		index, shard, id := args[1], args[2], args[3]
   486  
   487  		defer r.Body.Close()
   488  
   489  		err := i.shards.DeleteObject(r.Context(), index, shard, strfmt.UUID(id))
   490  		if err != nil {
   491  			http.Error(w, err.Error(), http.StatusInternalServerError)
   492  		}
   493  
   494  		w.WriteHeader(http.StatusNoContent)
   495  	})
   496  }
   497  
   498  func (i *indices) mergeObject() http.Handler {
   499  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   500  		args := i.regexpObject.FindStringSubmatch(r.URL.Path)
   501  		if len(args) != 4 {
   502  			http.Error(w, "invalid URI", http.StatusBadRequest)
   503  			return
   504  		}
   505  
   506  		index, shard, _ := args[1], args[2], args[3]
   507  
   508  		defer r.Body.Close()
   509  		ct, ok := IndicesPayloads.MergeDoc.CheckContentTypeHeaderReq(r)
   510  		if !ok {
   511  			http.Error(w, errors.Errorf("unexpected content type: %s", ct).Error(),
   512  				http.StatusUnsupportedMediaType)
   513  			return
   514  		}
   515  
   516  		bodyBytes, err := io.ReadAll(r.Body)
   517  		if err != nil {
   518  			http.Error(w, err.Error(), http.StatusInternalServerError)
   519  			return
   520  		}
   521  
   522  		mergeDoc, err := IndicesPayloads.MergeDoc.Unmarshal(bodyBytes)
   523  		if err != nil {
   524  			http.Error(w, err.Error(), http.StatusInternalServerError)
   525  			return
   526  		}
   527  
   528  		if err := i.shards.MergeObject(r.Context(), index, shard, mergeDoc); err != nil {
   529  			http.Error(w, err.Error(), http.StatusInternalServerError)
   530  			return
   531  		}
   532  
   533  		w.WriteHeader(http.StatusNoContent)
   534  	})
   535  }
   536  
   537  func (i *indices) getObjectsMulti() http.Handler {
   538  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   539  		args := i.regexpObjects.FindStringSubmatch(r.URL.Path)
   540  		if len(args) != 3 {
   541  			http.Error(w, fmt.Sprintf("invalid URI: %s", r.URL.Path),
   542  				http.StatusBadRequest)
   543  			return
   544  		}
   545  
   546  		index, shard := args[1], args[2]
   547  
   548  		defer r.Body.Close()
   549  
   550  		idsEncoded := r.URL.Query().Get("ids")
   551  		if idsEncoded == "" {
   552  			http.Error(w, "missing required url param 'ids'",
   553  				http.StatusBadRequest)
   554  			return
   555  		}
   556  
   557  		idsBytes, err := base64.StdEncoding.DecodeString(idsEncoded)
   558  		if err != nil {
   559  			http.Error(w, "base64 decode 'ids' param: "+err.Error(),
   560  				http.StatusBadRequest)
   561  			return
   562  		}
   563  
   564  		var ids []strfmt.UUID
   565  		if err := json.Unmarshal(idsBytes, &ids); err != nil {
   566  			http.Error(w, "unmarshal 'ids' param from json: "+err.Error(),
   567  				http.StatusBadRequest)
   568  			return
   569  		}
   570  
   571  		objs, err := i.shards.MultiGetObjects(r.Context(), index, shard, ids)
   572  		if err != nil {
   573  			http.Error(w, err.Error(), http.StatusInternalServerError)
   574  		}
   575  
   576  		objsBytes, err := IndicesPayloads.ObjectList.Marshal(objs)
   577  		if err != nil {
   578  			http.Error(w, err.Error(), http.StatusInternalServerError)
   579  		}
   580  
   581  		IndicesPayloads.ObjectList.SetContentTypeHeader(w)
   582  		w.Write(objsBytes)
   583  	})
   584  }
   585  
   586  func (i *indices) postSearchObjects() http.Handler {
   587  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   588  		args := i.regexpObjectsSearch.FindStringSubmatch(r.URL.Path)
   589  		if len(args) != 3 {
   590  			http.Error(w, "invalid URI", http.StatusBadRequest)
   591  			return
   592  		}
   593  
   594  		index, shard := args[1], args[2]
   595  
   596  		defer r.Body.Close()
   597  		reqPayload, err := io.ReadAll(r.Body)
   598  		if err != nil {
   599  			http.Error(w, "read request body: "+err.Error(), http.StatusInternalServerError)
   600  			return
   601  		}
   602  
   603  		ct, ok := IndicesPayloads.SearchParams.CheckContentTypeHeaderReq(r)
   604  		if !ok {
   605  			http.Error(w, errors.Errorf("unexpected content type: %s", ct).Error(),
   606  				http.StatusUnsupportedMediaType)
   607  			return
   608  		}
   609  
   610  		vector, targetVector, certainty, limit, filters, keywordRanking, sort, cursor, groupBy, additional, err := IndicesPayloads.SearchParams.
   611  			Unmarshal(reqPayload)
   612  		if err != nil {
   613  			http.Error(w, "unmarshal search params from json: "+err.Error(),
   614  				http.StatusBadRequest)
   615  			return
   616  		}
   617  
   618  		results, dists, err := i.shards.Search(r.Context(), index, shard,
   619  			vector, targetVector, certainty, limit, filters, keywordRanking, sort, cursor, groupBy, additional)
   620  		if err != nil {
   621  			http.Error(w, err.Error(), http.StatusInternalServerError)
   622  			return
   623  		}
   624  
   625  		resBytes, err := IndicesPayloads.SearchResults.Marshal(results, dists)
   626  		if err != nil {
   627  			http.Error(w, err.Error(), http.StatusInternalServerError)
   628  			return
   629  		}
   630  
   631  		IndicesPayloads.SearchResults.SetContentTypeHeader(w)
   632  		w.Write(resBytes)
   633  	})
   634  }
   635  
   636  func (i *indices) postReferences() http.Handler {
   637  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   638  		args := i.regexpReferences.FindStringSubmatch(r.URL.Path)
   639  		if len(args) != 3 {
   640  			http.Error(w, "invalid URI", http.StatusBadRequest)
   641  			return
   642  		}
   643  
   644  		index, shard := args[1], args[2]
   645  
   646  		defer r.Body.Close()
   647  		reqPayload, err := io.ReadAll(r.Body)
   648  		if err != nil {
   649  			http.Error(w, "read request body: "+err.Error(),
   650  				http.StatusInternalServerError)
   651  			return
   652  		}
   653  
   654  		ct, ok := IndicesPayloads.ReferenceList.CheckContentTypeHeaderReq(r)
   655  		if !ok {
   656  			http.Error(w, errors.Errorf("unexpected content type: %s", ct).Error(),
   657  				http.StatusUnsupportedMediaType)
   658  			return
   659  		}
   660  
   661  		refs, err := IndicesPayloads.ReferenceList.Unmarshal(reqPayload)
   662  		if err != nil {
   663  			http.Error(w, "read request body: "+err.Error(),
   664  				http.StatusInternalServerError)
   665  			return
   666  		}
   667  
   668  		errs := i.shards.BatchAddReferences(r.Context(), index, shard, refs)
   669  		errsJSON, err := IndicesPayloads.ErrorList.Marshal(errs)
   670  		if err != nil {
   671  			http.Error(w, err.Error(), http.StatusInternalServerError)
   672  			return
   673  		}
   674  
   675  		IndicesPayloads.ErrorList.SetContentTypeHeader(w)
   676  		w.Write(errsJSON)
   677  	})
   678  }
   679  
   680  func (i *indices) postAggregateObjects() http.Handler {
   681  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   682  		args := i.regexpObjectsAggregations.FindStringSubmatch(r.URL.Path)
   683  		if len(args) != 3 {
   684  			http.Error(w, "invalid URI", http.StatusBadRequest)
   685  			return
   686  		}
   687  
   688  		index, shard := args[1], args[2]
   689  
   690  		defer r.Body.Close()
   691  		reqPayload, err := io.ReadAll(r.Body)
   692  		if err != nil {
   693  			http.Error(w, "read request body: "+err.Error(),
   694  				http.StatusInternalServerError)
   695  			return
   696  		}
   697  
   698  		ct, ok := IndicesPayloads.AggregationParams.CheckContentTypeHeaderReq(r)
   699  		if !ok {
   700  			http.Error(w, errors.Errorf("unexpected content type: %s", ct).Error(),
   701  				http.StatusUnsupportedMediaType)
   702  			return
   703  		}
   704  
   705  		params, err := IndicesPayloads.AggregationParams.Unmarshal(reqPayload)
   706  		if err != nil {
   707  			http.Error(w, "read request body: "+err.Error(),
   708  				http.StatusInternalServerError)
   709  			return
   710  		}
   711  
   712  		aggRes, err := i.shards.Aggregate(r.Context(), index, shard, params)
   713  		if err != nil {
   714  			http.Error(w, err.Error(), http.StatusInternalServerError)
   715  			return
   716  		}
   717  
   718  		aggResBytes, err := IndicesPayloads.AggregationResult.Marshal(aggRes)
   719  		if err != nil {
   720  			http.Error(w, err.Error(), http.StatusInternalServerError)
   721  			return
   722  		}
   723  
   724  		IndicesPayloads.AggregationResult.SetContentTypeHeader(w)
   725  		w.Write(aggResBytes)
   726  	})
   727  }
   728  
   729  func (i *indices) postFindUUIDs() http.Handler {
   730  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   731  		args := i.regexpObjectsFind.FindStringSubmatch(r.URL.Path)
   732  		if len(args) != 3 {
   733  			http.Error(w, "invalid URI", http.StatusBadRequest)
   734  			return
   735  		}
   736  
   737  		index, shard := args[1], args[2]
   738  
   739  		defer r.Body.Close()
   740  		reqPayload, err := io.ReadAll(r.Body)
   741  		if err != nil {
   742  			http.Error(w, "read request body: "+err.Error(), http.StatusInternalServerError)
   743  			return
   744  		}
   745  
   746  		ct, ok := IndicesPayloads.FindUUIDsParams.CheckContentTypeHeaderReq(r)
   747  		if !ok {
   748  			http.Error(w, errors.Errorf("unexpected content type: %s", ct).Error(),
   749  				http.StatusUnsupportedMediaType)
   750  			return
   751  		}
   752  
   753  		filters, err := IndicesPayloads.FindUUIDsParams.
   754  			Unmarshal(reqPayload)
   755  		if err != nil {
   756  			http.Error(w, "unmarshal find doc ids params from json: "+err.Error(),
   757  				http.StatusBadRequest)
   758  			return
   759  		}
   760  
   761  		results, err := i.shards.FindUUIDs(r.Context(), index, shard, filters)
   762  		if err != nil {
   763  			http.Error(w, err.Error(), http.StatusInternalServerError)
   764  			return
   765  		}
   766  
   767  		resBytes, err := IndicesPayloads.FindUUIDsResults.Marshal(results)
   768  		if err != nil {
   769  			http.Error(w, err.Error(), http.StatusInternalServerError)
   770  			return
   771  		}
   772  
   773  		IndicesPayloads.FindUUIDsResults.SetContentTypeHeader(w)
   774  		w.Write(resBytes)
   775  	})
   776  }
   777  
   778  func (i *indices) putOverwriteObjects() http.Handler {
   779  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   780  		args := i.regexpObjectsOverwrite.FindStringSubmatch(r.URL.Path)
   781  		if len(args) != 3 {
   782  			http.Error(w, "invalid URI", http.StatusBadRequest)
   783  			return
   784  		}
   785  
   786  		index, shard := args[1], args[2]
   787  
   788  		defer r.Body.Close()
   789  		reqPayload, err := io.ReadAll(r.Body)
   790  		if err != nil {
   791  			http.Error(w, "read request body: "+err.Error(), http.StatusInternalServerError)
   792  			return
   793  		}
   794  
   795  		ct, ok := IndicesPayloads.VersionedObjectList.CheckContentTypeHeaderReq(r)
   796  		if !ok {
   797  			http.Error(w, errors.Errorf("unexpected content type: %s", ct).Error(),
   798  				http.StatusUnsupportedMediaType)
   799  			return
   800  		}
   801  
   802  		vobjs, err := IndicesPayloads.VersionedObjectList.Unmarshal(reqPayload)
   803  		if err != nil {
   804  			http.Error(w, "unmarshal overwrite objects params from json: "+err.Error(),
   805  				http.StatusBadRequest)
   806  			return
   807  		}
   808  
   809  		results, err := i.shards.OverwriteObjects(r.Context(), index, shard, vobjs)
   810  		if err != nil {
   811  			http.Error(w, "overwrite objects: "+err.Error(),
   812  				http.StatusInternalServerError)
   813  			return
   814  		}
   815  
   816  		resBytes, err := json.Marshal(results)
   817  		if err != nil {
   818  			http.Error(w, err.Error(), http.StatusInternalServerError)
   819  			return
   820  		}
   821  
   822  		w.Write(resBytes)
   823  	})
   824  }
   825  
   826  func (i *indices) getObjectsDigest() http.Handler {
   827  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   828  		args := i.regexObjectsDigest.FindStringSubmatch(r.URL.Path)
   829  		if len(args) != 3 {
   830  			http.Error(w, "invalid URI", http.StatusBadRequest)
   831  			return
   832  		}
   833  
   834  		index, shard := args[1], args[2]
   835  
   836  		defer r.Body.Close()
   837  		reqPayload, err := io.ReadAll(r.Body)
   838  		if err != nil {
   839  			http.Error(w, "read request body: "+err.Error(), http.StatusInternalServerError)
   840  			return
   841  		}
   842  
   843  		var ids []strfmt.UUID
   844  		if err := json.Unmarshal(reqPayload, &ids); err != nil {
   845  			http.Error(w, "unmarshal digest objects params from json: "+err.Error(),
   846  				http.StatusBadRequest)
   847  			return
   848  		}
   849  
   850  		results, err := i.shards.DigestObjects(r.Context(), index, shard, ids)
   851  		if err != nil {
   852  			http.Error(w, "digest objects: "+err.Error(),
   853  				http.StatusInternalServerError)
   854  			return
   855  		}
   856  
   857  		resBytes, err := json.Marshal(results)
   858  		if err != nil {
   859  			http.Error(w, err.Error(), http.StatusInternalServerError)
   860  			return
   861  		}
   862  
   863  		w.Write(resBytes)
   864  	})
   865  }
   866  
   867  func (i *indices) deleteObjects() http.Handler {
   868  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   869  		args := i.regexpObjects.FindStringSubmatch(r.URL.Path)
   870  		if len(args) != 3 {
   871  			http.Error(w, "invalid URI", http.StatusBadRequest)
   872  			return
   873  		}
   874  
   875  		index, shard := args[1], args[2]
   876  
   877  		defer r.Body.Close()
   878  		reqPayload, err := io.ReadAll(r.Body)
   879  		if err != nil {
   880  			http.Error(w, "read request body: "+err.Error(), http.StatusInternalServerError)
   881  			return
   882  		}
   883  
   884  		ct, ok := IndicesPayloads.BatchDeleteParams.CheckContentTypeHeaderReq(r)
   885  		if !ok {
   886  			http.Error(w, errors.Errorf("unexpected content type: %s", ct).Error(),
   887  				http.StatusUnsupportedMediaType)
   888  			return
   889  		}
   890  
   891  		uuids, dryRun, err := IndicesPayloads.BatchDeleteParams.
   892  			Unmarshal(reqPayload)
   893  		if err != nil {
   894  			http.Error(w, "unmarshal find doc ids params from json: "+err.Error(),
   895  				http.StatusBadRequest)
   896  			return
   897  		}
   898  
   899  		results := i.shards.DeleteObjectBatch(r.Context(), index, shard, uuids, dryRun)
   900  
   901  		resBytes, err := IndicesPayloads.BatchDeleteResults.Marshal(results)
   902  		if err != nil {
   903  			http.Error(w, err.Error(), http.StatusInternalServerError)
   904  			return
   905  		}
   906  
   907  		IndicesPayloads.BatchDeleteResults.SetContentTypeHeader(w)
   908  		w.Write(resBytes)
   909  	})
   910  }
   911  
   912  func (i *indices) getGetShardQueueSize() http.Handler {
   913  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   914  		args := i.regexpShardsQueueSize.FindStringSubmatch(r.URL.Path)
   915  		if len(args) != 3 {
   916  			http.Error(w, "invalid URI", http.StatusBadRequest)
   917  			return
   918  		}
   919  
   920  		index, shard := args[1], args[2]
   921  
   922  		defer r.Body.Close()
   923  
   924  		size, err := i.shards.GetShardQueueSize(r.Context(), index, shard)
   925  		if err != nil {
   926  			http.Error(w, err.Error(), http.StatusInternalServerError)
   927  		}
   928  
   929  		sizeBytes, err := IndicesPayloads.GetShardQueueSizeResults.Marshal(size)
   930  		if err != nil {
   931  			http.Error(w, err.Error(), http.StatusInternalServerError)
   932  		}
   933  
   934  		IndicesPayloads.GetShardQueueSizeResults.SetContentTypeHeader(w)
   935  		w.Write(sizeBytes)
   936  	})
   937  }
   938  
   939  func (i *indices) getGetShardStatus() http.Handler {
   940  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   941  		args := i.regexpShardsStatus.FindStringSubmatch(r.URL.Path)
   942  		if len(args) != 3 {
   943  			http.Error(w, "invalid URI", http.StatusBadRequest)
   944  			return
   945  		}
   946  
   947  		index, shard := args[1], args[2]
   948  
   949  		defer r.Body.Close()
   950  
   951  		status, err := i.shards.GetShardStatus(r.Context(), index, shard)
   952  		if err != nil {
   953  			http.Error(w, err.Error(), http.StatusInternalServerError)
   954  		}
   955  
   956  		statusBytes, err := IndicesPayloads.GetShardStatusResults.Marshal(status)
   957  		if err != nil {
   958  			http.Error(w, err.Error(), http.StatusInternalServerError)
   959  		}
   960  
   961  		IndicesPayloads.GetShardStatusResults.SetContentTypeHeader(w)
   962  		w.Write(statusBytes)
   963  	})
   964  }
   965  
   966  func (i *indices) postUpdateShardStatus() http.Handler {
   967  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   968  		args := i.regexpShardsStatus.FindStringSubmatch(r.URL.Path)
   969  		if len(args) != 3 {
   970  			http.Error(w, "invalid URI", http.StatusBadRequest)
   971  			return
   972  		}
   973  
   974  		index, shard := args[1], args[2]
   975  
   976  		defer r.Body.Close()
   977  		reqPayload, err := io.ReadAll(r.Body)
   978  		if err != nil {
   979  			http.Error(w, "read request body: "+err.Error(), http.StatusInternalServerError)
   980  			return
   981  		}
   982  
   983  		ct, ok := IndicesPayloads.UpdateShardStatusParams.CheckContentTypeHeaderReq(r)
   984  		if !ok {
   985  			http.Error(w, errors.Errorf("unexpected content type: %s", ct).Error(),
   986  				http.StatusUnsupportedMediaType)
   987  			return
   988  		}
   989  
   990  		targetStatus, err := IndicesPayloads.UpdateShardStatusParams.
   991  			Unmarshal(reqPayload)
   992  		if err != nil {
   993  			http.Error(w, "unmarshal find doc ids params from json: "+err.Error(),
   994  				http.StatusBadRequest)
   995  			return
   996  		}
   997  
   998  		err = i.shards.UpdateShardStatus(r.Context(), index, shard, targetStatus)
   999  		if err != nil {
  1000  			http.Error(w, err.Error(), http.StatusInternalServerError)
  1001  			return
  1002  		}
  1003  	})
  1004  }
  1005  
  1006  func (i *indices) postShardFile() http.Handler {
  1007  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1008  		args := i.regexpShardFiles.FindStringSubmatch(r.URL.Path)
  1009  		if len(args) != 4 {
  1010  			http.Error(w, "invalid URI", http.StatusBadRequest)
  1011  			return
  1012  		}
  1013  
  1014  		index, shard, filename := args[1], args[2], args[3]
  1015  
  1016  		ct, ok := IndicesPayloads.ShardFiles.CheckContentTypeHeaderReq(r)
  1017  		if !ok {
  1018  			http.Error(w, errors.Errorf("unexpected content type: %s", ct).Error(),
  1019  				http.StatusUnsupportedMediaType)
  1020  			return
  1021  		}
  1022  
  1023  		fp, err := i.shards.FilePutter(r.Context(), index, shard, filename)
  1024  		if err != nil {
  1025  			http.Error(w, err.Error(), http.StatusInternalServerError)
  1026  			return
  1027  		}
  1028  
  1029  		defer fp.Close()
  1030  		n, err := io.Copy(fp, r.Body)
  1031  		if err != nil {
  1032  			http.Error(w, err.Error(), http.StatusInternalServerError)
  1033  			return
  1034  		}
  1035  
  1036  		fmt.Printf("%s/%s/%s n=%d\n", index, shard, filename, n)
  1037  
  1038  		w.WriteHeader(http.StatusNoContent)
  1039  	})
  1040  }
  1041  
  1042  func (i *indices) postShard() http.Handler {
  1043  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1044  		args := i.regexpShard.FindStringSubmatch(r.URL.Path)
  1045  		fmt.Println(args)
  1046  		if len(args) != 3 {
  1047  			http.Error(w, "invalid URI", http.StatusBadRequest)
  1048  			return
  1049  		}
  1050  
  1051  		index, shard := args[1], args[2]
  1052  
  1053  		err := i.shards.CreateShard(r.Context(), index, shard)
  1054  		if err != nil {
  1055  			http.Error(w, err.Error(), http.StatusInternalServerError)
  1056  			return
  1057  		}
  1058  
  1059  		w.WriteHeader(http.StatusCreated)
  1060  	})
  1061  }
  1062  
  1063  func (i *indices) putShardReinit() http.Handler {
  1064  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1065  		args := i.regexpShardReinit.FindStringSubmatch(r.URL.Path)
  1066  		fmt.Println(args)
  1067  		if len(args) != 3 {
  1068  			http.Error(w, "invalid URI", http.StatusBadRequest)
  1069  			return
  1070  		}
  1071  
  1072  		index, shard := args[1], args[2]
  1073  
  1074  		err := i.shards.ReInitShard(r.Context(), index, shard)
  1075  		if err != nil {
  1076  			http.Error(w, err.Error(), http.StatusInternalServerError)
  1077  			return
  1078  		}
  1079  
  1080  		w.WriteHeader(http.StatusNoContent)
  1081  	})
  1082  }