github.com/attic-labs/noms@v0.0.0-20210827224422-e5fa29d95e8b/samples/go/nomdex/nomdex_find.go (about)

     1  // Copyright 2016 Attic Labs, Inc. All rights reserved.
     2  // Licensed under the Apache License, version 2.0:
     3  // http://www.apache.org/licenses/LICENSE-2.0
     4  
     5  package main
     6  
     7  import (
     8  	"fmt"
     9  	"io"
    10  
    11  	"github.com/attic-labs/kingpin"
    12  
    13  	"github.com/attic-labs/noms/go/config"
    14  	"github.com/attic-labs/noms/go/datas"
    15  	"github.com/attic-labs/noms/go/types"
    16  	"github.com/attic-labs/noms/go/util/outputpager"
    17  )
    18  
    19  var longFindHelp = `'nomdex find' retrieves and prints objects that satisfy the 'query' argument.
    20  
    21  Indexes are built using the 'nomdex up' command. For information about building
    22  indexes, see: nomdex up -h
    23  
    24  Objects that have been indexed can be quickly found using the nomdex query
    25  language. For example, consider objects with the following type:
    26  
    27  struct Person {
    28    name String,
    29    geopos struct GeoPos {
    30       latitude Number,
    31       longitude Number,
    32    }
    33  }
    34  
    35  Objects of this type can be indexed on the name, latitude and longitude fields
    36  with the following commands:
    37      nomdex up --in-path ~/nomsdb::people.value --by .name --out-ds by-name
    38      nomdex up --in-path ~/nomsdb::people.value --by .geopos.latitude --out-ds by-lat
    39      nomdex up --in-path ~/nomsdb::people.value --by .geopos.longitude --out-ds by-lng
    40  
    41  The following query could be used to find all people with an address near the
    42  equator:
    43      nomdex find 'by-lat >= -1.0 and by-lat <= 1.0'
    44  
    45  We could also get a list of all people who live near the equator whose name begins with "A":
    46      nomdex find '(by-name >= "A" and by-name < "B") and (by-lat >= -1.0 and by-lat <= 1.0)'
    47  
    48  The query language is simple. It currently supports the following relational operators:
    49      <, <=, >, >=, =, !=
    50  Relational expressions are always of the form:
    51      <index> <relational operator> <constant>   e.g. personId >= 2000.
    52  
    53  Indexes are the name given by the --out-ds argument in the 'nomdex up' command.
    54  Constants are either "strings" (in quotes) or numbers (e.g. 3, 3000, -2, -2.5,
    55  3.147, etc).
    56  
    57  Relational expressions can be combined using the "and" and "or" operators.
    58  Parentheses can (and should) be used to ensure that the evaluation is done in
    59  the desired order.
    60  `
    61  
    62  var dbPath = ""
    63  var query = ""
    64  
    65  func registerFind() {
    66  	cmd := kingpin.Command("find", "Search an index")
    67  	cmd.Flag("db", "Database containing index").Required().StringVar(&dbPath)
    68  	cmd.Arg("query", "query to evalute").Required().StringVar(&query)
    69  	outputpager.RegisterOutputpagerFlags(cmd)
    70  }
    71  
    72  func runFind() int {
    73  	cfg := config.NewResolver()
    74  	db, err := cfg.GetDatabase(dbPath)
    75  	if printError(err, "Unable to open database\n\terror: ") {
    76  		return 1
    77  	}
    78  	defer db.Close()
    79  
    80  	im := &indexManager{db: db, indexes: map[string]types.Map{}}
    81  	expr, err := parseQuery(query, im)
    82  	if err != nil {
    83  		fmt.Printf("err: %s\n", err)
    84  		return 1
    85  	}
    86  
    87  	pgr := outputpager.Start()
    88  	defer pgr.Stop()
    89  
    90  	iter := expr.iterator(im)
    91  	cnt := 0
    92  	if iter != nil {
    93  		for v := iter.Next(); v != nil; v = iter.Next() {
    94  			types.WriteEncodedValue(pgr.Writer, v)
    95  			fmt.Fprintf(pgr.Writer, "\n")
    96  			cnt++
    97  		}
    98  	}
    99  	fmt.Fprintf(pgr.Writer, "Found %d objects\n", cnt)
   100  
   101  	return 0
   102  }
   103  
   104  func printObjects(w io.Writer, index types.Map, ranges queryRangeSlice) {
   105  	cnt := 0
   106  	first := true
   107  	printObjectForRange := func(index types.Map, r queryRange) {
   108  		index.IterFrom(r.lower.value, func(k, v types.Value) bool {
   109  			if first && r.lower.value != nil && !r.lower.include && r.lower.value.Equals(k) {
   110  				return false
   111  			}
   112  			if r.upper.value != nil {
   113  				if !r.upper.include && r.upper.value.Equals(k) {
   114  					return true
   115  				}
   116  				if r.upper.value.Less(k) {
   117  					return true
   118  				}
   119  			}
   120  			s := v.(types.Set)
   121  			s.IterAll(func(v types.Value) {
   122  				types.WriteEncodedValue(w, v)
   123  				fmt.Fprintf(w, "\n")
   124  				cnt++
   125  			})
   126  			return false
   127  		})
   128  	}
   129  	for _, r := range ranges {
   130  		printObjectForRange(index, r)
   131  	}
   132  	fmt.Fprintf(w, "Found %d objects\n", cnt)
   133  }
   134  
   135  func openIndex(idxName string, im *indexManager) error {
   136  	if _, hasIndex := im.indexes[idxName]; hasIndex {
   137  		return nil
   138  	}
   139  
   140  	ds := im.db.GetDataset(idxName)
   141  	commit, ok := ds.MaybeHead()
   142  	if !ok {
   143  		return fmt.Errorf("index '%s' not found", idxName)
   144  	}
   145  
   146  	index, ok := commit.Get(datas.ValueField).(types.Map)
   147  	if !ok {
   148  		return fmt.Errorf("Value of commit at '%s' is not a valid index", idxName)
   149  	}
   150  
   151  	// Todo: make this type be Map<String | Number>, Set<Value>> once Issue #2326 gets resolved and
   152  	// IsSubtype() returns the correct value.
   153  	typ := types.MakeMapType(
   154  		types.MakeUnionType(types.StringType, types.NumberType),
   155  		types.ValueType)
   156  
   157  	if !types.IsValueSubtypeOf(index, typ) {
   158  		return fmt.Errorf("%s does not point to a suitable index type:", idxName)
   159  	}
   160  
   161  	im.indexes[idxName] = index
   162  	return nil
   163  }