github.com/cayleygraph/cayley@v0.7.7/graph/sql/mysql/mysql.go (about)

     1  package mysql
     2  
     3  import (
     4  	"database/sql"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/cayleygraph/cayley/clog"
     9  	"github.com/cayleygraph/cayley/graph"
    10  	"github.com/cayleygraph/cayley/graph/log"
    11  	csql "github.com/cayleygraph/cayley/graph/sql"
    12  	"github.com/cayleygraph/quad"
    13  	"github.com/go-sql-driver/mysql"
    14  )
    15  
    16  const Type = "mysql"
    17  
    18  var QueryDialect = csql.QueryDialect{
    19  	RegexpOp: "REGEXP",
    20  	FieldQuote: func(name string) string {
    21  		return "`" + name + "`"
    22  	},
    23  	Placeholder: func(n int) string { return "?" },
    24  }
    25  
    26  func init() {
    27  	csql.Register(Type, csql.Registration{
    28  		Driver:               "mysql",
    29  		HashType:             fmt.Sprintf(`BINARY(%d)`, quad.HashSize),
    30  		BytesType:            `BLOB`,
    31  		HorizonType:          `SERIAL`,
    32  		TimeType:             `DATETIME(6)`,
    33  		QueryDialect:         QueryDialect,
    34  		NoOffsetWithoutLimit: true,
    35  		Error: func(err error) error {
    36  			return err
    37  		},
    38  		Estimated: nil,
    39  		RunTx:     runTxMysql,
    40  	})
    41  }
    42  
    43  func runTxMysql(tx *sql.Tx, nodes []graphlog.NodeUpdate, quads []graphlog.QuadUpdate, opts graph.IgnoreOpts) error {
    44  	// update node ref counts and insert nodes
    45  	var (
    46  		// prepared statements for each value type
    47  		insertValue = make(map[csql.ValueType]*sql.Stmt)
    48  		updateValue *sql.Stmt
    49  	)
    50  	for _, n := range nodes {
    51  		if n.RefInc >= 0 {
    52  			nodeKey, values, err := csql.NodeValues(csql.NodeHash{n.Hash}, n.Val)
    53  			if err != nil {
    54  				return err
    55  			}
    56  			values = append([]interface{}{n.RefInc}, values...)
    57  			values = append(values, n.RefInc) // one more time for UPDATE
    58  			stmt, ok := insertValue[nodeKey]
    59  			if !ok {
    60  				var ph = make([]string, len(values)-1) // excluding last increment
    61  				for i := range ph {
    62  					ph[i] = "?"
    63  				}
    64  				stmt, err = tx.Prepare(`INSERT INTO nodes(refs, hash, ` +
    65  					strings.Join(nodeKey.Columns(), ", ") +
    66  					`) VALUES (` + strings.Join(ph, ", ") +
    67  					`) ON DUPLICATE KEY UPDATE refs = refs + ?;`)
    68  				if err != nil {
    69  					return err
    70  				}
    71  				insertValue[nodeKey] = stmt
    72  			}
    73  			_, err = stmt.Exec(values...)
    74  			err = convInsertError(err)
    75  			if err != nil {
    76  				clog.Errorf("couldn't exec INSERT statement: %v", err)
    77  				return err
    78  			}
    79  		} else {
    80  			panic("unexpected node update")
    81  		}
    82  	}
    83  	for _, s := range insertValue {
    84  		s.Close()
    85  	}
    86  	if s := updateValue; s != nil {
    87  		s.Close()
    88  	}
    89  	insertValue = nil
    90  	updateValue = nil
    91  
    92  	// now we can deal with quads
    93  	ignore := ""
    94  	if opts.IgnoreDup {
    95  		ignore = " IGNORE"
    96  	}
    97  
    98  	var (
    99  		insertQuad *sql.Stmt
   100  		err        error
   101  	)
   102  	for _, d := range quads {
   103  		dirs := make([]interface{}, 0, len(quad.Directions))
   104  		for _, h := range d.Quad.Dirs() {
   105  			dirs = append(dirs, csql.NodeHash{h}.SQLValue())
   106  		}
   107  		if !d.Del {
   108  			if insertQuad == nil {
   109  				insertQuad, err = tx.Prepare(`INSERT` + ignore + ` INTO quads(subject_hash, predicate_hash, object_hash, label_hash, ts) VALUES (?, ?, ?, ?, now());`)
   110  				if err != nil {
   111  					return err
   112  				}
   113  				insertValue = make(map[csql.ValueType]*sql.Stmt)
   114  			}
   115  			_, err := insertQuad.Exec(dirs...)
   116  			err = convInsertError(err)
   117  			if err != nil {
   118  				if _, ok := err.(*graph.DeltaError); !ok {
   119  					clog.Errorf("couldn't exec INSERT statement: %v", err)
   120  				}
   121  				return err
   122  			}
   123  		} else {
   124  			panic("unexpected quad delete")
   125  		}
   126  	}
   127  	return nil
   128  }
   129  
   130  func convInsertError(err error) error {
   131  	if err == nil {
   132  		return nil
   133  	}
   134  	if e, ok := err.(*mysql.MySQLError); ok {
   135  		if e.Number == 1062 {
   136  			// TODO: reference to delta
   137  			return &graph.DeltaError{Err: graph.ErrQuadExists}
   138  		}
   139  	}
   140  	return err
   141  }