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

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