package sql
import (
"database/sql"
"github.com/cayleygraph/cayley/graph"
"fmt"
)
var types = make(map[string]Registration)
func Register(name string, f Registration) {
if f.Driver == "" {
panic("no sql driver in type definition")
}
types[name] = f
registerQuadStore(name, name)
}
type Registration struct {
Driver string // sql driver to use on dial
HashType string // type for hash fields
BytesType string // type for binary fields
TimeType string // type for datetime fields
HorizonType string // type for horizon counter
NodesTableExtra string // extra SQL to append to nodes table definition
ConditionalIndexes bool // database supports conditional indexes
FillFactor bool // database supports fill percent on indexes
NoForeignKeys bool // database has no support for FKs
FieldQuote func(name string) string // function to escape field names
Placeholder func(n int) string // function to generate n-th query placeholder
Error func(error) error // error conversion function
Estimated func(table string) string // query that string that returns an estimated number of rows in table
RunTx func(tx *sql.Tx, in []graph.Delta, opts graph.IgnoreOpts) error
NoSchemaChangesInTx bool
}
func (r Registration) nodesTable() string {
htyp := r.HashType
if htyp == "" {
htyp = "BYTEA"
}
btyp := r.BytesType
if btyp == "" {
btyp = "BYTEA"
}
ttyp := r.TimeType
if ttyp == "" {
ttyp = "timestamp with time zone"
}
end := "\n);"
if r.NodesTableExtra != "" {
end = ",\n" + r.NodesTableExtra + end
}
return `CREATE TABLE nodes (
hash ` + htyp + ` PRIMARY KEY,
value ` + btyp + `,
value_string TEXT,
datatype TEXT,
language TEXT,
iri BOOLEAN,
bnode BOOLEAN,
value_int BIGINT,
value_bool BOOLEAN,
value_float double precision,
value_time ` + ttyp +
end
}
func (r Registration) quadsTable() string {
htyp := r.HashType
if htyp == "" {
htyp = "BYTEA"
}
hztyp := r.HorizonType
if hztyp == "" {
hztyp = "SERIAL"
}
return `CREATE TABLE quads (
horizon ` + hztyp + ` PRIMARY KEY,
subject_hash ` + htyp + ` NOT NULL,
predicate_hash ` + htyp + ` NOT NULL,
object_hash ` + htyp + ` NOT NULL,
label_hash ` + htyp + `,
id BIGINT,
ts timestamp
);`
}
func (r Registration) quadIndexes(options graph.Options) []string {
indexes := make([]string, 0, 10)
if r.ConditionalIndexes {
indexes = append(indexes,
`CREATE UNIQUE INDEX spo_unique ON quads (subject_hash, predicate_hash, object_hash) WHERE label_hash IS NULL;`,
`CREATE UNIQUE INDEX spol_unique ON quads (subject_hash, predicate_hash, object_hash, label_hash) WHERE label_hash IS NOT NULL;`,
)
} else {
indexes = append(indexes,
`CREATE UNIQUE INDEX spo_unique ON quads (subject_hash, predicate_hash, object_hash);`,
`CREATE UNIQUE INDEX spol_unique ON quads (subject_hash, predicate_hash, object_hash, label_hash);`,
)
}
if !r.NoForeignKeys {
indexes = append(indexes,
`ALTER TABLE quads ADD CONSTRAINT subject_hash_fk FOREIGN KEY (subject_hash) REFERENCES nodes (hash);`,
`ALTER TABLE quads ADD CONSTRAINT predicate_hash_fk FOREIGN KEY (predicate_hash) REFERENCES nodes (hash);`,
`ALTER TABLE quads ADD CONSTRAINT object_hash_fk FOREIGN KEY (object_hash) REFERENCES nodes (hash);`,
`ALTER TABLE quads ADD CONSTRAINT label_hash_fk FOREIGN KEY (label_hash) REFERENCES nodes (hash);`,
)
}
if r.FillFactor {
const defaultFillFactor = 50
factor, ok, _ := options.IntKey("db_fill_factor")
if !ok {
factor = defaultFillFactor
}
indexes = append(indexes,
fmt.Sprintf(`CREATE INDEX spo_index ON quads (subject_hash) WITH (FILLFACTOR = %d);`, factor),
fmt.Sprintf(`CREATE INDEX pos_index ON quads (predicate_hash) WITH (FILLFACTOR = %d);`, factor),
fmt.Sprintf(`CREATE INDEX osp_index ON quads (object_hash) WITH (FILLFACTOR = %d);`, factor),
)
} else {
indexes = append(indexes,
`CREATE INDEX spo_index ON quads (subject_hash);`,
`CREATE INDEX pos_index ON quads (predicate_hash);`,
`CREATE INDEX osp_index ON quads (object_hash);`,
)
}
return indexes
}