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  }