go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/service/datastore/dumper/dumper.go (about) 1 // Copyright 2016 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package dumper implements a very VERY dumb datastore-dumping debugging aid. 16 // You shouldn't plan on having this work with the production datastore with any 17 // appreciable amount of data. 18 // 19 // This will take an arbitrary query (or even a query for every entity in the 20 // entire datastore), and print every entity to some output stream. 21 package dumper 22 23 import ( 24 "context" 25 "fmt" 26 "io" 27 "os" 28 "sort" 29 "strings" 30 31 ds "go.chromium.org/luci/gae/service/datastore" 32 ) 33 34 // Key is a key into a PropFilterMap 35 type Key struct { 36 Kind string 37 PropName string 38 } 39 40 // A PropFilterMap maps from Kind+PropertyName tuples to a formatting function. You 41 // may use this to specially format particular properties. 42 type PropFilterMap map[Key]func(ds.Property) string 43 44 // KindFilterMap maps from a Kind to a formatting function. You may use this to 45 // specially format particular Kinds. If this function returns an empty string, 46 // the default formatting function (including any PropFilterMap entries) will be 47 // used. 48 type KindFilterMap map[string]func(*ds.Key, ds.PropertyMap) string 49 50 // Config is a configured dumper. 51 type Config struct { 52 // OutStream is the output stream to use. If this is nil, os.Stdout will be 53 // used. 54 OutStream io.Writer 55 56 // WithSpecial, if true, includes entities which have kinds that begin and 57 // end with "__". By default, these entities are skipped. 58 WithSpecial bool 59 60 // PropFilters is an optional property filter map for controlling the 61 // rendering of certain Kind/Property values. 62 PropFilters PropFilterMap 63 64 // KindFilters is an optional kind filter for controlling the rendering of 65 // certain Kind values. 66 KindFilters KindFilterMap 67 } 68 69 // Query will dump everything matching the provided query. 70 // 71 // If the provided query is nil, a kindless query without any filters will be 72 // used. 73 func (cfg Config) Query(c context.Context, q *ds.Query) (n int, err error) { 74 if q == nil { 75 q = ds.NewQuery("") 76 } 77 78 out := cfg.OutStream 79 if out == nil { 80 out = os.Stdout 81 } 82 83 fmtVal := func(kind, name string, prop ds.Property) string { 84 if fn := cfg.PropFilters[Key{kind, name}]; fn != nil { 85 return fn(prop) 86 } 87 return prop.String() 88 } 89 90 prnt := func(format string, args ...any) (err error) { 91 var amt int 92 amt, err = fmt.Fprintf(out, format, args...) 93 n += amt 94 return 95 } 96 97 prop := func(kind, name string, pdata ds.PropertyData) (err error) { 98 switch t := pdata.(type) { 99 case ds.Property: 100 return prnt(" %q: %s\n", name, fmtVal(kind, name, t)) 101 102 case ds.PropertySlice: 103 if len(t) <= 1 { 104 return prnt(" %q: [%s]\n", name, fmtVal(kind, name, t[0])) 105 } 106 if err = prnt(" %q: [\n %s", name, fmtVal(kind, name, t[0])); err != nil { 107 return 108 } 109 for _, v := range t[1:] { 110 if err = prnt(",\n %s", fmtVal(kind, name, v)); err != nil { 111 return 112 } 113 } 114 115 default: 116 return fmt.Errorf("unknown PropertyData %T", t) 117 } 118 return prnt("\n ]\n") 119 } 120 121 err = ds.Run(c, q, func(pm ds.PropertyMap) error { 122 key := ds.GetMetaDefault(pm, "key", nil).(*ds.Key) 123 if !cfg.WithSpecial && strings.HasPrefix(key.Kind(), "__") && strings.HasSuffix(key.Kind(), "__") { 124 return nil 125 } 126 if err := prnt("\n%s:\n", key); err != nil { 127 return err 128 } 129 pm, _ = pm.Save(false) 130 131 // See if we have a KindFilter for this 132 if flt, ok := cfg.KindFilters[key.Kind()]; ok { 133 if kindOut := flt(key, pm); kindOut != "" { 134 for _, l := range strings.Split(kindOut, "\n") { 135 if err := prnt(" %s\n", l); err != nil { 136 return err 137 } 138 } 139 return nil 140 } 141 } 142 143 keys := make([]string, 0, len(pm)) 144 for k := range pm { 145 keys = append(keys, k) 146 } 147 sort.Strings(keys) 148 for _, k := range keys { 149 if err := prop(key.Kind(), k, pm[k]); err != nil { 150 return err 151 } 152 } 153 return nil 154 }) 155 return 156 } 157 158 // Query dumps the provided query to stdout without special entities and with 159 // default rendering. 160 func Query(c context.Context, q *ds.Query) { 161 Config{}.Query(c, q) 162 } 163 164 // All dumps all entities to stdout without special entities and with default 165 // rendering. 166 func All(c context.Context) { 167 Config{}.Query(c, nil) 168 }