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

     1  package postgres
     2  
     3  import (
     4  	"database/sql"
     5  	"fmt"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/cayleygraph/cayley/clog"
    10  	"github.com/cayleygraph/cayley/graph"
    11  	"github.com/cayleygraph/cayley/graph/log"
    12  	csql "github.com/cayleygraph/cayley/graph/sql"
    13  	"github.com/cayleygraph/quad"
    14  	"github.com/lib/pq"
    15  )
    16  
    17  const Type = "postgres"
    18  
    19  var QueryDialect = csql.QueryDialect{
    20  	RegexpOp:   "~",
    21  	FieldQuote: pq.QuoteIdentifier,
    22  	Placeholder: func(n int) string {
    23  		return fmt.Sprintf("$%d", n)
    24  	},
    25  }
    26  
    27  func init() {
    28  	csql.Register(Type, csql.Registration{
    29  		Driver:             "postgres",
    30  		HashType:           `BYTEA`,
    31  		BytesType:          `BYTEA`,
    32  		HorizonType:        `BIGSERIAL`,
    33  		TimeType:           `timestamp with time zone`,
    34  		QueryDialect:       QueryDialect,
    35  		ConditionalIndexes: true,
    36  		FillFactor:         true,
    37  		Error:              ConvError,
    38  		Estimated: func(table string) string {
    39  			return "SELECT reltuples::BIGINT AS estimate FROM pg_class WHERE relname='" + table + "';"
    40  		},
    41  		RunTx: RunTxPostgres,
    42  	})
    43  }
    44  
    45  func ConvError(err error) error {
    46  	e, ok := err.(*pq.Error)
    47  	if !ok {
    48  		return err
    49  	}
    50  	switch e.Code {
    51  	case "42P07":
    52  		return graph.ErrDatabaseExists
    53  	}
    54  	return err
    55  }
    56  
    57  func convInsertError(err error) error {
    58  	if err == nil {
    59  		return err
    60  	}
    61  	if pe, ok := err.(*pq.Error); ok {
    62  		if pe.Code == "23505" {
    63  			// TODO: reference to delta
    64  			return &graph.DeltaError{Err: graph.ErrQuadExists}
    65  		}
    66  	}
    67  	return err
    68  }
    69  
    70  //func copyFromPG(tx *sql.Tx, in []graph.Delta, opts graph.IgnoreOpts) error {
    71  //	panic("broken")
    72  //	stmt, err := tx.Prepare(pq.CopyIn("quads", "subject", "predicate", "object", "label", "id", "ts", "subject_hash", "predicate_hash", "object_hash", "label_hash"))
    73  //	if err != nil {
    74  //		clog.Errorf("couldn't prepare COPY statement: %v", err)
    75  //		return err
    76  //	}
    77  //	for _, d := range in {
    78  //		s, p, o, l, err := marshalQuadDirections(d.Quad)
    79  //		if err != nil {
    80  //			clog.Errorf("couldn't marshal quads: %v", err)
    81  //			return err
    82  //		}
    83  //		_, err = stmt.Exec(
    84  //			s,
    85  //			p,
    86  //			o,
    87  //			l,
    88  //			d.ID.Int(),
    89  //			d.Timestamp,
    90  //			hashOf(d.Quad.Subject),
    91  //			hashOf(d.Quad.Predicate),
    92  //			hashOf(d.Quad.Object),
    93  //			hashOf(d.Quad.Label),
    94  //		)
    95  //		if err != nil {
    96  //			err = convInsertErrorPG(err)
    97  //			clog.Errorf("couldn't execute COPY statement: %v", err)
    98  //			return err
    99  //		}
   100  //	}
   101  //	_, err = stmt.Exec()
   102  //	if err != nil {
   103  //		err = convInsertErrorPG(err)
   104  //		return err
   105  //	}
   106  //	_ = stmt.Close() // COPY will be closed on last Exec, this will return non-nil error in all cases
   107  //	return nil
   108  //}
   109  
   110  func RunTxPostgres(tx *sql.Tx, nodes []graphlog.NodeUpdate, quads []graphlog.QuadUpdate, opts graph.IgnoreOpts) error {
   111  	return RunTx(tx, nodes, quads, opts, "")
   112  }
   113  
   114  func RunTx(tx *sql.Tx, nodes []graphlog.NodeUpdate, quads []graphlog.QuadUpdate, opts graph.IgnoreOpts, onConflict string) error {
   115  	// update node ref counts and insert nodes
   116  	var (
   117  		// prepared statements for each value type
   118  		insertValue = make(map[csql.ValueType]*sql.Stmt)
   119  		updateValue *sql.Stmt
   120  	)
   121  	for _, n := range nodes {
   122  		if n.RefInc >= 0 {
   123  			nodeKey, values, err := csql.NodeValues(csql.NodeHash{n.Hash}, n.Val)
   124  			if err != nil {
   125  				return err
   126  			}
   127  			values = append([]interface{}{n.RefInc}, values...)
   128  			stmt, ok := insertValue[nodeKey]
   129  			if !ok {
   130  				var ph = make([]string, len(values))
   131  				for i := range ph {
   132  					ph[i] = "$" + strconv.FormatInt(int64(i)+1, 10)
   133  				}
   134  				stmt, err = tx.Prepare(`INSERT INTO nodes(refs, hash, ` +
   135  					strings.Join(nodeKey.Columns(), ", ") +
   136  					`) VALUES (` + strings.Join(ph, ", ") +
   137  					`) ON CONFLICT (hash) DO UPDATE SET refs = nodes.refs + EXCLUDED.refs;`)
   138  				if err != nil {
   139  					return err
   140  				}
   141  				insertValue[nodeKey] = stmt
   142  			}
   143  			_, err = stmt.Exec(values...)
   144  			err = convInsertError(err)
   145  			if err != nil {
   146  				clog.Errorf("couldn't exec INSERT statement: %v", err)
   147  				return err
   148  			}
   149  		} else {
   150  			panic("unexpected node update")
   151  		}
   152  	}
   153  	for _, s := range insertValue {
   154  		s.Close()
   155  	}
   156  	if s := updateValue; s != nil {
   157  		s.Close()
   158  	}
   159  	insertValue = nil
   160  	updateValue = nil
   161  
   162  	// now we can deal with quads
   163  
   164  	// TODO: copy
   165  	//if allAdds && !opts.IgnoreDup {
   166  	//	return qs.copyFrom(tx, in, opts)
   167  	//}
   168  
   169  	end := ";"
   170  	if opts.IgnoreDup {
   171  		end = ` ON CONFLICT ` + onConflict + ` DO NOTHING;`
   172  	}
   173  
   174  	var (
   175  		insertQuad *sql.Stmt
   176  		err        error
   177  	)
   178  	for _, d := range quads {
   179  		dirs := make([]interface{}, 0, len(quad.Directions))
   180  		for _, h := range d.Quad.Dirs() {
   181  			dirs = append(dirs, csql.NodeHash{h}.SQLValue())
   182  		}
   183  		if !d.Del {
   184  			if insertQuad == nil {
   185  				insertQuad, err = tx.Prepare(`INSERT INTO quads(subject_hash, predicate_hash, object_hash, label_hash, ts) VALUES ($1, $2, $3, $4, now())` + end)
   186  				if err != nil {
   187  					return err
   188  				}
   189  				insertValue = make(map[csql.ValueType]*sql.Stmt)
   190  			}
   191  			_, err := insertQuad.Exec(dirs...)
   192  			err = convInsertError(err)
   193  			if err != nil {
   194  				if _, ok := err.(*graph.DeltaError); !ok {
   195  					clog.Errorf("couldn't exec INSERT statement: %v", err)
   196  				}
   197  				return err
   198  			}
   199  		} else {
   200  			panic("unexpected quad delete")
   201  		}
   202  	}
   203  	return nil
   204  }