github.com/cayleygraph/cayley@v0.7.7/schema/writer.go (about)

     1  package schema
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  
     7  	"github.com/cayleygraph/quad"
     8  )
     9  
    10  // WriteAsQuads writes a single value in form of quads into specified quad writer.
    11  //
    12  // It returns an identifier of the object in the output sub-graph. If an object has
    13  // an annotated ID field, it's value will be converted to quad.Value and returned.
    14  // Otherwise, a new BNode will be generated using GenerateID function.
    15  //
    16  // See LoadTo for a list of quads mapping rules.
    17  func (c *Config) WriteAsQuads(w quad.Writer, o interface{}) (quad.Value, error) {
    18  	wr := c.newWriter(w)
    19  	return wr.writeAsQuads(reflect.ValueOf(o))
    20  }
    21  
    22  type writer struct {
    23  	c    *Config
    24  	w    quad.Writer
    25  	seen map[uintptr]quad.Value
    26  }
    27  
    28  func (c *Config) newWriter(w quad.Writer) *writer {
    29  	return &writer{c: c, w: w, seen: make(map[uintptr]quad.Value)}
    30  }
    31  
    32  func (w *writer) writeQuad(s, p, o quad.Value, rev bool) error {
    33  	if rev {
    34  		s, o = o, s
    35  	}
    36  	return w.w.WriteQuad(quad.Quad{Subject: s, Predicate: p, Object: o, Label: w.c.Label})
    37  }
    38  
    39  // writeOneValReflect writes a set of quads corresponding to a value. It may omit writing quads if value is zero.
    40  func (w *writer) writeOneValReflect(id quad.Value, pred quad.Value, rv reflect.Value, rev bool) error {
    41  	if isZero(rv) {
    42  		return nil
    43  	}
    44  	// write field value and get an ID
    45  	sid, err := w.writeAsQuads(rv)
    46  	if err != nil {
    47  		return err
    48  	}
    49  	// write a quad pointing to this value
    50  	return w.writeQuad(id, pred, sid, rev)
    51  }
    52  
    53  func (w *writer) writeTypeInfo(id quad.Value, rt reflect.Type) error {
    54  	iri := getTypeIRI(rt)
    55  	if iri == quad.IRI("") {
    56  		return nil
    57  	}
    58  	return w.writeQuad(id, w.c.iri(iriType), w.c.iri(iri), false)
    59  }
    60  
    61  func (w *writer) writeValueAs(id quad.Value, rv reflect.Value, pref string, rules fieldRules) error {
    62  	switch kind := rv.Kind(); kind {
    63  	case reflect.Ptr, reflect.Map:
    64  		ptr := rv.Pointer()
    65  		if _, ok := w.seen[ptr]; ok {
    66  			return nil
    67  		}
    68  		w.seen[ptr] = id
    69  		if kind == reflect.Ptr {
    70  			rv = rv.Elem()
    71  		}
    72  	}
    73  	rt := rv.Type()
    74  	if err := w.writeTypeInfo(id, rt); err != nil {
    75  		return err
    76  	}
    77  	for i := 0; i < rt.NumField(); i++ {
    78  		f := rt.Field(i)
    79  		if f.Anonymous {
    80  			if err := w.writeValueAs(id, rv.Field(i), pref+f.Name+".", rules); err != nil {
    81  				return err
    82  			}
    83  			continue
    84  		}
    85  		switch r := rules[pref+f.Name].(type) {
    86  		case constraintRule:
    87  			s, o := id, quad.Value(r.Val)
    88  			if r.Rev {
    89  				s, o = o, s
    90  			}
    91  			if err := w.writeQuad(s, r.Pred, o, false); err != nil {
    92  				return err
    93  			}
    94  		case saveRule:
    95  			if f.Type.Kind() == reflect.Slice {
    96  				sl := rv.Field(i)
    97  				for j := 0; j < sl.Len(); j++ {
    98  					if err := w.writeOneValReflect(id, r.Pred, sl.Index(j), r.Rev); err != nil {
    99  						return err
   100  					}
   101  				}
   102  			} else {
   103  				fv := rv.Field(i)
   104  				if !r.Opt && isZero(fv) {
   105  					return ErrReqFieldNotSet{Field: f.Name}
   106  				}
   107  				if err := w.writeOneValReflect(id, r.Pred, fv, r.Rev); err != nil {
   108  					return err
   109  				}
   110  			}
   111  		}
   112  	}
   113  	return nil
   114  }
   115  
   116  func (w *writer) writeAsQuads(rv reflect.Value) (quad.Value, error) {
   117  	rt := rv.Type()
   118  	// if node is a primitive - return directly
   119  	if rt.Implements(reflQuadValue) {
   120  		return rv.Interface().(quad.Value), nil
   121  	}
   122  	prv := rv
   123  	kind := rt.Kind()
   124  	// check if we've seen this node already
   125  	switch kind {
   126  	case reflect.Ptr, reflect.Map:
   127  		ptr := prv.Pointer()
   128  		if sid, ok := w.seen[ptr]; ok {
   129  			return sid, nil
   130  		}
   131  		if kind == reflect.Ptr {
   132  			rv = rv.Elem()
   133  			rt = rv.Type()
   134  			kind = rt.Kind()
   135  		}
   136  	}
   137  	// check if it's a type that quads package supports
   138  	// note, that it may be a struct such as time.Time
   139  	if val, ok := quad.AsValue(rv.Interface()); ok {
   140  		return val, nil
   141  	} else if kind == reflect.String {
   142  		return quad.String(rv.String()), nil
   143  	} else if kind == reflect.Int || kind == reflect.Uint ||
   144  		kind == reflect.Int32 || kind == reflect.Uint32 ||
   145  		kind == reflect.Int16 || kind == reflect.Uint16 ||
   146  		kind == reflect.Int8 || kind == reflect.Uint8 {
   147  		return quad.Int(rv.Int()), nil
   148  	} else if kind == reflect.Float64 || kind == reflect.Float32 {
   149  		return quad.Float(rv.Float()), nil
   150  	} else if kind == reflect.Bool {
   151  		return quad.Bool(rv.Bool()), nil
   152  	}
   153  	// TODO(dennwc): support maps
   154  	if kind != reflect.Struct {
   155  		return nil, fmt.Errorf("unsupported type: %v", rt)
   156  	}
   157  	// get conversion rules for this struct type
   158  	rules, err := w.c.rulesFor(rt)
   159  	if err != nil {
   160  		return nil, fmt.Errorf("can't load rules: %v", err)
   161  	}
   162  	if len(rules) == 0 {
   163  		return nil, fmt.Errorf("no rules for struct: %v", rt)
   164  	}
   165  	// get an ID from the struct value
   166  	id, err := w.c.idFor(rules, rt, rv, "")
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  	if id == nil {
   171  		id = w.c.genID(prv.Interface())
   172  	}
   173  	// save a node ID to avoid loops
   174  	switch prv.Kind() {
   175  	case reflect.Ptr, reflect.Map:
   176  		ptr := prv.Pointer()
   177  		w.seen[ptr] = id
   178  	}
   179  	if err = w.writeValueAs(id, rv, "", rules); err != nil {
   180  		return nil, err
   181  	}
   182  	return id, nil
   183  }