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 }