sql >> Database teknologi >  >> NoSQL >> MongoDB

Effektiv personsøgning i MongoDB ved hjælp af mgo

Desværre er mgo.v2 driveren leverer ikke API-kald for at specificere cursor.min() .

Men der er en løsning. mgo.Database type giver en Database.Run() metode til at køre MongoDB-kommandoer. De tilgængelige kommandoer og deres dokumentation kan findes her:Databasekommandoer

Startende med MongoDB 3.2, en ny find kommandoen er tilgængelig, som kan bruges til at udføre forespørgsler, og den understøtter specificering af min argument, der angiver den første indeksindgang, man starter med at vise resultater fra.

Godt. Det, vi skal gøre, er efter hver batch (dokumenter på en side) at generere min dokument fra det sidste dokument i forespørgselsresultatet, som skal indeholde værdierne for den indeksindgang, der blev brugt til at udføre forespørgslen, og derefter kan den næste batch (dokumenterne på næste side) hentes ved at indstille denne min. indeksindgang før til at udføre forespørgslen.

Denne indeksindgang – lad os kalde det markør fra nu af – kan være kodet til en string og sendes til klienten sammen med resultaterne, og når klienten vil have den næste side, sender han markøren tilbage siger, at han vil have resultater, der starter efter denne markør.

Gør det manuelt (den "hårde" måde)

Kommandoen, der skal udføres, kan være i forskellige former, men kommandonavnet (find ) skal være først i det marshalerede resultat, så vi bruger bson.D (som bevarer orden i modsætning til bson.M ):

limit := 10
cmd := bson.D{
    {Name: "find", Value: "users"},
    {Name: "filter", Value: bson.M{"country": "USA"}},
    {Name: "sort", Value: []bson.D{
        {Name: "name", Value: 1},
        {Name: "_id", Value: 1},
    },
    {Name: "limit", Value: limit},
    {Name: "batchSize", Value: limit},
    {Name: "singleBatch", Value: true},
}
if min != nil {
    // min is inclusive, must skip first (which is the previous last)
    cmd = append(cmd,
        bson.DocElem{Name: "skip", Value: 1},
        bson.DocElem{Name: "min", Value: min},
    )
}

Resultatet af at udføre en MongoDB find kommando med Database.Run() kan optages med følgende type:

var res struct {
    OK       int `bson:"ok"`
    WaitedMS int `bson:"waitedMS"`
    Cursor   struct {
        ID         interface{} `bson:"id"`
        NS         string      `bson:"ns"`
        FirstBatch []bson.Raw  `bson:"firstBatch"`
    } `bson:"cursor"`
}

db := session.DB("")
if err := db.Run(cmd, &res); err != nil {
    // Handle error (abort)
}

Vi har nu resultaterne, men i et udsnit af typen []bson.Raw . Men vi vil have det i et udsnit af typen []*User . Det er her Collection.NewIter() kommer praktisk. Det kan transformere (unmarshal) en værdi af typen []bson.Raw til enhver type, vi normalt sender til Query.All() eller Iter.All() . Godt. Lad os se det:

firstBatch := res.Cursor.FirstBatch
var users []*User
err = db.C("users").NewIter(nil, firstBatch, 0, nil).All(&users)

Vi har nu brugerne af den næste side. Kun én ting tilbage:generering af markøren, der skal bruges til at få den efterfølgende side, hvis vi nogensinde får brug for den:

if len(users) > 0 {
    lastUser := users[len(users)-1]
    cursorData := []bson.D{
        {Name: "country", Value: lastUser.Country},
        {Name: "name", Value: lastUser.Name},
        {Name: "_id", Value: lastUser.ID},
    }
} else {
    // No more users found, use the last cursor
}

Det er alt sammen godt, men hvordan konverterer vi en cursorData til string og omvendt? Vi kan bruge bson.Marshal() og bson.Unmarshal() kombineret med base64-kodning; brugen af ​​base64.RawURLEncoding vil give os en websikker markørstreng, som kan føjes til URL-forespørgsler uden at undslippe.

Her er et eksempel på implementering:

// CreateCursor returns a web-safe cursor string from the specified fields.
// The returned cursor string is safe to include in URL queries without escaping.
func CreateCursor(cursorData bson.D) (string, error) {
    // bson.Marshal() never returns error, so I skip a check and early return
    // (but I do return the error if it would ever happen)
    data, err := bson.Marshal(cursorData)
    return base64.RawURLEncoding.EncodeToString(data), err
}

// ParseCursor parses the cursor string and returns the cursor data.
func ParseCursor(c string) (cursorData bson.D, err error) {
    var data []byte
    if data, err = base64.RawURLEncoding.DecodeString(c); err != nil {
        return
    }

    err = bson.Unmarshal(data, &cursorData)
    return
}

Og vi har endelig vores effektive, men ikke så korte MongoDB mgo personsøgning funktionalitet. Læs videre...

Brug af github.com/icza/minquery (den "nemme" måde)

Den manuelle måde er ret lang; det kan gøres generelt og automatiseret . Det er her github.com/icza/minquery kommer ind i billedet (afsløring:Jeg er forfatteren ). Det giver en indpakning til at konfigurere og udføre en MongoDB find kommando, som giver dig mulighed for at angive en markør, og efter at have udført forespørgslen, giver den dig den nye markør tilbage, som skal bruges til at forespørge på den næste batch af resultater. Indpakningen er MinQuery type, som minder meget om mgo.Query men det understøtter at specificere MongoDB's min via MinQuery.Cursor() metode.

Ovenstående løsning ved hjælp af minquery ser sådan ud:

q := minquery.New(session.DB(""), "users", bson.M{"country" : "USA"}).
    Sort("name", "_id").Limit(10)
// If this is not the first page, set cursor:
// getLastCursor() represents your logic how you acquire the last cursor.
if cursor := getLastCursor(); cursor != "" {
    q = q.Cursor(cursor)
}

var users []*User
newCursor, err := q.All(&users, "country", "name", "_id")

Og det er alt. newCursor er den markør, der skal bruges til at hente den næste batch.

Bemærk #1: Når du kalder MinQuery.All() , skal du angive navnene på markørfelterne, dette vil blive brugt til at bygge markørdataene (og i sidste ende markørstrengen) fra.

Bemærk #2: Hvis du henter delvise resultater (ved at bruge MinQuery.Select() ), skal du inkludere alle de felter, der er en del af markøren (indeksindgangen), selvom du ikke har til hensigt at bruge dem direkte, ellers MinQuery.All() vil ikke have alle værdierne for markørfelterne, og det vil derfor ikke være i stand til at oprette den korrekte markørværdi.

Tjek pakkedokumentet til minquery her:https://godoc.org/github.com/icza/minquery, den er ret kort og forhåbentlig ren.




  1. Sådan udvikler du applikationer til MongoDB og Redpanda ved hjælp af Docker Compose

  2. MongoDB Database Deployment Automation

  3. Spring data mongodb - Muligheden 'markør' er påkrævet

  4. Keras forudser ikke at vende tilbage inden for selleri opgaven