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) ## 3.4.1 (September 1st 2016)
- [PR #175](https://github.com/rqlite/rqlite/pull/175): Simplify error handling of Update Peers API. - [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. - [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 dsn string
var onDisk bool var onDisk bool
var noVerifySelect bool var noVerifySelect bool
var noFKCheck bool
var raftSnapThreshold uint64 var raftSnapThreshold uint64
var raftHeartbeatTimeout string var raftHeartbeatTimeout string
var showVersion bool var showVersion bool
@ -99,6 +100,7 @@ func init() {
flag.StringVar(&dsn, "dsn", "", `SQLite DSN parameters. E.g. "cache=shared&mode=memory"`) 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(&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(&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.BoolVar(&showVersion, "version", false, "Show version information and exit")
flag.StringVar(&raftHeartbeatTimeout, "rafttimeout", "1s", "Raft heartbeat timeout") flag.StringVar(&raftHeartbeatTimeout, "rafttimeout", "1s", "Raft heartbeat timeout")
flag.Uint64Var(&raftSnapThreshold, "raftsnap", 8192, "Number of outstanding log entries to trigger snapshot") 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()) log.Fatalf("failed to determine absolute data path: %s", err.Error())
} }
dbConf := store.NewDBConfig(dsn, !onDisk) dbConf := store.NewDBConfig(dsn, !onDisk)
dbConf.NoFK = noFKCheck
store := store.New(dbConf, dataPath, raftTn) store := store.New(dbConf, dataPath, raftTn)
if err := store.Open(joinAddr == ""); err != nil { if err := store.Open(joinAddr == ""); err != nil {
log.Fatalf("failed to open store: %s", err.Error()) log.Fatalf("failed to open store: %s", err.Error())

@ -13,6 +13,11 @@ import (
const bkDelay = 250 const bkDelay = 250
const (
fkChecksEnabled = "PRAGMA foreign_keys=ON"
fkChecksDisabled = "PRAGMA foreign_keys=OFF"
)
// DBVersion is the SQLite version. // DBVersion is the SQLite version.
var DBVersion string var DBVersion string
@ -123,6 +128,19 @@ func open(dbPath string) (*DB, error) {
}, nil }, 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. // Execute executes queries that modify the database.
func (db *DB) Execute(queries []string, tx, xTime bool) ([]*Result, error) { func (db *DB) Execute(queries []string, tx, xTime bool) ([]*Result, error) {
type Execer interface { 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) { func Test_PartialFail(t *testing.T) {
db, path := mustCreateDatabase() db, path := mustCreateDatabase()
defer db.Close() defer db.Close()

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

Loading…
Cancel
Save