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

     1  package sql
     2  
     3  import (
     4  	"database/sql"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/cayleygraph/cayley/graph"
     9  	"github.com/cayleygraph/cayley/graph/log"
    10  	"github.com/cayleygraph/quad"
    11  )
    12  
    13  var types = make(map[string]Registration)
    14  
    15  func Register(name string, f Registration) {
    16  	if f.Driver == "" {
    17  		panic("no sql driver in type definition")
    18  	}
    19  	types[name] = f
    20  
    21  	registerQuadStore(name, name)
    22  }
    23  
    24  type Registration struct {
    25  	Driver             string // sql driver to use on dial
    26  	HashType           string // type for hash fields
    27  	BytesType          string // type for binary fields
    28  	TimeType           string // type for datetime fields
    29  	HorizonType        string // type for horizon counter
    30  	NodesTableExtra    string // extra SQL to append to nodes table definition
    31  	ConditionalIndexes bool   // database supports conditional indexes
    32  	FillFactor         bool   // database supports fill percent on indexes
    33  	NoForeignKeys      bool   // database has no support for FKs
    34  
    35  	QueryDialect
    36  	NoOffsetWithoutLimit bool // SELECT ... OFFSET can be used only with LIMIT
    37  
    38  	Error               func(error) error         // error conversion function
    39  	Estimated           func(table string) string // query that string that returns an estimated number of rows in table
    40  	RunTx               func(tx *sql.Tx, nodes []graphlog.NodeUpdate, quads []graphlog.QuadUpdate, opts graph.IgnoreOpts) error
    41  	TxRetry             func(tx *sql.Tx, stmts func() error) error
    42  	NoSchemaChangesInTx bool
    43  }
    44  
    45  func (r Registration) nodesTable() string {
    46  	htyp := r.HashType
    47  	if htyp == "" {
    48  		htyp = "BYTEA"
    49  	}
    50  	btyp := r.BytesType
    51  	if btyp == "" {
    52  		btyp = "BYTEA"
    53  	}
    54  	ttyp := r.TimeType
    55  	if ttyp == "" {
    56  		ttyp = "timestamp with time zone"
    57  	}
    58  	end := "\n);"
    59  	if r.NodesTableExtra != "" {
    60  		end = ",\n" + r.NodesTableExtra + end
    61  	}
    62  	return `CREATE TABLE nodes (
    63  	hash ` + htyp + ` PRIMARY KEY,
    64  	refs INT NOT NULL,
    65  	value ` + btyp + `,
    66  	value_string TEXT,
    67  	datatype TEXT,
    68  	language TEXT,
    69  	iri BOOLEAN,
    70  	bnode BOOLEAN,
    71  	value_int BIGINT,
    72  	value_bool BOOLEAN,
    73  	value_float double precision,
    74  	value_time ` + ttyp +
    75  		end
    76  }
    77  
    78  func (r Registration) quadsTable() string {
    79  	htyp := r.HashType
    80  	if htyp == "" {
    81  		htyp = "BYTEA"
    82  	}
    83  	hztyp := r.HorizonType
    84  	if hztyp == "" {
    85  		hztyp = "SERIAL"
    86  	}
    87  	return `CREATE TABLE quads (
    88  	horizon ` + hztyp + ` PRIMARY KEY,
    89  	subject_hash ` + htyp + ` NOT NULL,
    90  	predicate_hash ` + htyp + ` NOT NULL,
    91  	object_hash ` + htyp + ` NOT NULL,
    92  	label_hash ` + htyp + `,
    93  	ts timestamp
    94  );`
    95  }
    96  
    97  func (r Registration) quadIndexes(options graph.Options) []string {
    98  	indexes := make([]string, 0, 10)
    99  	if r.ConditionalIndexes {
   100  		indexes = append(indexes,
   101  			`CREATE UNIQUE INDEX spo_unique ON quads (subject_hash, predicate_hash, object_hash) WHERE label_hash IS NULL;`,
   102  			`CREATE UNIQUE INDEX spol_unique ON quads (subject_hash, predicate_hash, object_hash, label_hash) WHERE label_hash IS NOT NULL;`,
   103  		)
   104  	} else {
   105  		indexes = append(indexes,
   106  			`CREATE UNIQUE INDEX spo_unique ON quads (subject_hash, predicate_hash, object_hash);`,
   107  			`CREATE UNIQUE INDEX spol_unique ON quads (subject_hash, predicate_hash, object_hash, label_hash);`,
   108  		)
   109  	}
   110  	if !r.NoForeignKeys {
   111  		indexes = append(indexes,
   112  			`ALTER TABLE quads ADD CONSTRAINT subject_hash_fk FOREIGN KEY (subject_hash) REFERENCES nodes (hash);`,
   113  			`ALTER TABLE quads ADD CONSTRAINT predicate_hash_fk FOREIGN KEY (predicate_hash) REFERENCES nodes (hash);`,
   114  			`ALTER TABLE quads ADD CONSTRAINT object_hash_fk FOREIGN KEY (object_hash) REFERENCES nodes (hash);`,
   115  			`ALTER TABLE quads ADD CONSTRAINT label_hash_fk FOREIGN KEY (label_hash) REFERENCES nodes (hash);`,
   116  		)
   117  	}
   118  	quadIndexes := [][3]quad.Direction{
   119  		{quad.Subject, quad.Predicate, quad.Object},
   120  		{quad.Object, quad.Predicate, quad.Subject},
   121  		{quad.Predicate, quad.Object, quad.Subject},
   122  		{quad.Object, quad.Subject, quad.Predicate},
   123  	}
   124  	factor, _ := options.IntKey("db_fill_factor", 50)
   125  	for _, ind := range quadIndexes {
   126  		var (
   127  			name string
   128  			cols []string
   129  		)
   130  		for _, d := range ind {
   131  			name += string(d.Prefix())
   132  			cols = append(cols, d.String()+"_hash")
   133  		}
   134  		q := fmt.Sprintf(`CREATE INDEX %s_index ON quads (%s)`,
   135  			name, strings.Join(cols, ", "))
   136  		if r.FillFactor {
   137  			q += fmt.Sprintf(" WITH (FILLFACTOR = %d)", factor)
   138  		}
   139  		indexes = append(indexes, q+";")
   140  	}
   141  	return indexes
   142  }