1
0
Fork 0

Support controlling FK constraints at startup

This was possible previously, but would need to be set everytime on
startup via the API. This change allows it to set at startup AND enables
foreign constraint checking by default.
master
Philip O'Toole 8 years ago
parent edf29e897f
commit c40fff4d46

@ -1,3 +1,6 @@
## 3.5.0 (September 5th 2016)
- [PR #185](https://github.com/rqlite/rqlite/pull/185): Enable foreign key constraints by default.
## 3.4.1 (September 1st 2016)
- [PR #175](https://github.com/rqlite/rqlite/pull/175): Simplify error handling of Update Peers API.
- [PR #170](https://github.com/rqlite/rqlite/pull/170): Log any failure to call `Serve()` on HTTP service.

@ -77,6 +77,7 @@ var pprofEnabled bool
var dsn string
var onDisk bool
var noVerifySelect bool
var noFKCheck bool
var raftSnapThreshold uint64
var raftHeartbeatTimeout string
var showVersion bool
@ -99,6 +100,7 @@ func init() {
flag.StringVar(&dsn, "dsn", "", `SQLite DSN parameters. E.g. "cache=shared&mode=memory"`)
flag.BoolVar(&onDisk, "ondisk", false, "Use an on-disk SQLite database")
flag.BoolVar(&noVerifySelect, "nosel", false, "Don't verify that all queries begin with SELECT")
flag.BoolVar(&noFKCheck, "nofk", false, "Don't enforce foreign key constraints")
flag.BoolVar(&showVersion, "version", false, "Show version information and exit")
flag.StringVar(&raftHeartbeatTimeout, "rafttimeout", "1s", "Raft heartbeat timeout")
flag.Uint64Var(&raftSnapThreshold, "raftsnap", 8192, "Number of outstanding log entries to trigger snapshot")
@ -186,6 +188,8 @@ func main() {
log.Fatalf("failed to determine absolute data path: %s", err.Error())
}
dbConf := store.NewDBConfig(dsn, !onDisk)
dbConf.NoFK = noFKCheck
store := store.New(dbConf, dataPath, raftTn)
if err := store.Open(joinAddr == ""); err != nil {
log.Fatalf("failed to open store: %s", err.Error())

@ -13,6 +13,11 @@ import (
const bkDelay = 250
const (
fkChecksEnabled = "PRAGMA foreign_keys=ON"
fkChecksDisabled = "PRAGMA foreign_keys=OFF"
)
// DBVersion is the SQLite version.
var DBVersion string
@ -123,6 +128,19 @@ func open(dbPath string) (*DB, error) {
}, nil
}
// EnableFKConstraints allows control of foreign key constraint checks.
func (db *DB) EnableFKConstraints(e bool) error {
var q string
if e {
q = fkChecksEnabled
} else {
q = fkChecksDisabled
}
_, err := db.sqlite3conn.Exec(q, nil)
return err
}
// Execute executes queries that modify the database.
func (db *DB) Execute(queries []string, tx, xTime bool) ([]*Result, error) {
type Execer interface {

@ -244,6 +244,47 @@ func Test_SimpleFailingStatements(t *testing.T) {
}
}
func Test_ForeignKeyConstraints(t *testing.T) {
db, path := mustCreateDatabase()
defer db.Close()
defer os.Remove(path)
_, err := db.Execute([]string{"CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, ref INTEGER REFERENCES foo(id))"}, false, false)
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
// Explicitly disable constraints.
if err := db.EnableFKConstraints(false); err != nil {
t.Fatalf("failed to enable foreign key constraints: %s", err.Error())
}
stmts := []string{
`INSERT INTO foo(id, ref) VALUES(1, 2)`,
}
r, err := db.Execute(stmts, false, false)
if err != nil {
t.Fatalf("failed to execute FK test statement: %s", err.Error())
}
if exp, got := `[{"last_insert_id":1,"rows_affected":1}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
// Explicitly enable constraints.
if err := db.EnableFKConstraints(true); err != nil {
t.Fatalf("failed to enable foreign key constraints: %s", err.Error())
}
stmts = []string{
`INSERT INTO foo(id, ref) VALUES(1, 3)`,
}
r, err = db.Execute(stmts, false, false)
if err != nil {
t.Fatalf("failed to execute FK test statement: %s", err.Error())
}
if exp, got := `[{"error":"UNIQUE constraint failed: foo.id"}]`, asJSON(r); exp != got {
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
}
}
func Test_PartialFail(t *testing.T) {
db, path := mustCreateDatabase()
defer db.Close()

@ -138,6 +138,7 @@ func (c *clusterMeta) AddrForPeer(addr string) string {
type DBConfig struct {
DSN string // Any custom DSN
Memory bool // Whether the database is in-memory only.
NoFK bool // Disable foreign key constraints
}
// NewDBConfig returns a new DB config instance.
@ -207,6 +208,10 @@ func (s *Store) Open(enableSingle bool) error {
}
s.logger.Println("SQLite in-memory database opened")
}
if err := db.EnableFKConstraints(!s.dbConf.NoFK); err != nil {
return err
}
s.logger.Printf("SQLite foreign key constraints %s", enabledFromBool(!s.dbConf.NoFK))
s.db = db
// Setup Raft configuration.
@ -384,8 +389,9 @@ func (s *Store) WaitForAppliedIndex(idx uint64, timeout time.Duration) error {
// Stats returns stats for the store.
func (s *Store) Stats() (map[string]interface{}, error) {
dbStatus := map[string]interface{}{
"dns": s.dbConf.DSN,
"version": sql.DBVersion,
"dns": s.dbConf.DSN,
"fk_constraints": enabledFromBool(!s.dbConf.NoFK),
"version": sql.DBVersion,
}
if !s.dbConf.Memory {
dbStatus["path"] = s.dbPath
@ -774,3 +780,11 @@ func readPeersJSON(path string) ([]string, error) {
return peers, nil
}
// enabledFromBool converts bool to "enabled" or "disabled".
func enabledFromBool(b bool) string {
if b {
return "enabled"
}
return "disabled"
}

Loading…
Cancel
Save