mirror of
https://github.com/dolthub/dolt.git
synced 2026-02-04 18:49:00 -06:00
Added verify-constraints command
This commit is contained in:
committed by
Daylon Wilkins
parent
331a5a6ab5
commit
293be61d02
79
bats/verify-constraints.bats
Normal file
79
bats/verify-constraints.bats
Normal file
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env bats
|
||||
load $BATS_TEST_DIRNAME/helper/common.bash
|
||||
|
||||
setup() {
|
||||
setup_common
|
||||
dolt sql <<SQL
|
||||
CREATE TABLE parent1 (
|
||||
pk BIGINT PRIMARY KEY,
|
||||
v1 BIGINT,
|
||||
INDEX (v1)
|
||||
);
|
||||
CREATE TABLE parent2 (
|
||||
pk BIGINT PRIMARY KEY,
|
||||
v1 BIGINT,
|
||||
INDEX (v1)
|
||||
);
|
||||
CREATE TABLE child1 (
|
||||
pk BIGINT PRIMARY KEY,
|
||||
parent1_v1 BIGINT,
|
||||
parent2_v1 BIGINT,
|
||||
CONSTRAINT child1_parent1 FOREIGN KEY (parent1_v1) REFERENCES parent1 (v1),
|
||||
CONSTRAINT child1_parent2 FOREIGN KEY (parent2_v1) REFERENCES parent2 (v1)
|
||||
);
|
||||
CREATE TABLE child2 (
|
||||
pk BIGINT PRIMARY KEY,
|
||||
parent2_v1 BIGINT,
|
||||
CONSTRAINT child2_parent2 FOREIGN KEY (parent2_v1) REFERENCES parent2 (v1)
|
||||
);
|
||||
INSERT INTO parent1 VALUES (1,1), (2,2), (3,3);
|
||||
INSERT INTO parent2 VALUES (1,1), (2,2), (3,3);
|
||||
INSERT INTO child1 VALUES (1,1,1), (2,2,2);
|
||||
INSERT INTO child2 VALUES (2,2), (3,3);
|
||||
SQL
|
||||
}
|
||||
|
||||
teardown() {
|
||||
teardown_common
|
||||
}
|
||||
|
||||
@test "verify-constraints: Constraints verified" {
|
||||
dolt verify-constraints child1 child2
|
||||
}
|
||||
|
||||
@test "verify-constraints: One table fails" {
|
||||
dolt sql <<SQL
|
||||
SET foreign_key_checks=0;
|
||||
DELETE FROM parent1 WHERE pk = 1;
|
||||
SET foreign_key_checks=1;
|
||||
SQL
|
||||
run dolt verify-constraints child1
|
||||
[ "$status" -eq "1" ]
|
||||
[[ "$output" =~ "child1_parent1" ]] || false
|
||||
dolt verify-constraints child2
|
||||
run dolt verify-constraints child1 child2
|
||||
[ "$status" -eq "1" ]
|
||||
[[ "$output" =~ "child1_parent1" ]] || false
|
||||
[[ ! "$output" =~ "child1_parent2" ]] || false
|
||||
[[ ! "$output" =~ "child2_parent2" ]] || false
|
||||
}
|
||||
|
||||
@test "verify-constraints: Two tables fail" {
|
||||
dolt sql <<SQL
|
||||
SET foreign_key_checks=0;
|
||||
DELETE FROM parent2 WHERE pk = 2;
|
||||
SET foreign_key_checks=1;
|
||||
SQL
|
||||
run dolt verify-constraints child1
|
||||
[ "$status" -eq "1" ]
|
||||
[[ "$output" =~ "child1_parent2" ]] || false
|
||||
[[ ! "$output" =~ "child1_parent1" ]] || false
|
||||
run dolt verify-constraints child2
|
||||
[ "$status" -eq "1" ]
|
||||
[[ "$output" =~ "child2_parent2" ]] || fals
|
||||
run dolt verify-constraints child1 child2
|
||||
[ "$status" -eq "1" ]
|
||||
[[ "$output" =~ "child1_parent2" ]] || false
|
||||
[[ "$output" =~ "child2_parent2" ]] || false
|
||||
[[ ! "$output" =~ "child1_parent1" ]] || false
|
||||
}
|
||||
134
go/cmd/dolt/commands/verify_constraints.go
Normal file
134
go/cmd/dolt/commands/verify_constraints.go
Normal file
@@ -0,0 +1,134 @@
|
||||
// Copyright 2020 Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/dolthub/dolt/go/cmd/dolt/cli"
|
||||
"github.com/dolthub/dolt/go/cmd/dolt/errhand"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/env"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/table"
|
||||
"github.com/dolthub/dolt/go/libraries/utils/argparser"
|
||||
"github.com/dolthub/dolt/go/libraries/utils/filesys"
|
||||
)
|
||||
|
||||
var verifyConstraintsDocs = cli.CommandDocumentationContent{
|
||||
ShortDesc: `Verifies a table's constraints'`,
|
||||
LongDesc: `This command verifies that the defined constraints on the given table(s)—such as a foreign key—are correct and satisfied.`,
|
||||
Synopsis: []string{`{{.LessThan}}table{{.GreaterThan}}...`},
|
||||
}
|
||||
|
||||
type VerifyConstraintsCmd struct{}
|
||||
|
||||
var _ cli.Command = VerifyConstraintsCmd{}
|
||||
var _ cli.HiddenCommand = VerifyConstraintsCmd{}
|
||||
|
||||
func (cmd VerifyConstraintsCmd) Name() string {
|
||||
return "verify-constraints"
|
||||
}
|
||||
|
||||
func (cmd VerifyConstraintsCmd) Description() string {
|
||||
return "Command to verify that the constraints on the given table(s) are satisfied."
|
||||
}
|
||||
|
||||
func (cmd VerifyConstraintsCmd) CreateMarkdown(fs filesys.Filesys, path, commandStr string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd VerifyConstraintsCmd) createArgParser() *argparser.ArgParser {
|
||||
ap := argparser.NewArgParser()
|
||||
ap.ArgListHelp = append(ap.ArgListHelp, [2]string{"table", "The table to check constraints on."})
|
||||
return ap
|
||||
}
|
||||
|
||||
func (cmd VerifyConstraintsCmd) Exec(ctx context.Context, commandStr string, args []string, dEnv *env.DoltEnv) int {
|
||||
ap := cmd.createArgParser()
|
||||
help, usage := cli.HelpAndUsagePrinters(cli.GetCommandDocumentation(commandStr, verifyConstraintsDocs, ap))
|
||||
apr := cli.ParseArgs(ap, args, help)
|
||||
|
||||
if apr.NArg() == 0 {
|
||||
usage()
|
||||
return 0
|
||||
}
|
||||
tableNames := apr.Args()
|
||||
working, err := dEnv.WorkingRoot(ctx)
|
||||
if err != nil {
|
||||
return HandleVErrAndExitCode(errhand.BuildDError("Unable to get working.").AddCause(err).Build(), nil)
|
||||
}
|
||||
fkColl, err := working.GetForeignKeyCollection(ctx)
|
||||
if err != nil {
|
||||
return HandleVErrAndExitCode(errhand.BuildDError("Unable to get foreign keys.").AddCause(err).Build(), nil)
|
||||
}
|
||||
|
||||
var accumulatedConstraintErrors []string
|
||||
for _, givenTableName := range tableNames {
|
||||
tbl, tableName, ok, err := working.GetTableInsensitive(ctx, givenTableName)
|
||||
if err != nil {
|
||||
return HandleVErrAndExitCode(errhand.BuildDError("Unable to get table %s.", givenTableName).AddCause(err).Build(), nil)
|
||||
}
|
||||
if !ok {
|
||||
return HandleVErrAndExitCode(errhand.BuildDError("Table %s does not exist.", givenTableName).Build(), nil)
|
||||
}
|
||||
tblSch, err := tbl.GetSchema(ctx)
|
||||
if err != nil {
|
||||
return HandleVErrAndExitCode(errhand.BuildDError("Unable to get schema for %s.", tableName).AddCause(err).Build(), nil)
|
||||
}
|
||||
fks, _ := fkColl.KeysForTable(tableName)
|
||||
|
||||
for _, fk := range fks {
|
||||
childIdx := tblSch.Indexes().GetByName(fk.TableIndex)
|
||||
childIdxRowData, err := tbl.GetIndexRowData(ctx, fk.TableIndex)
|
||||
if err != nil {
|
||||
return HandleVErrAndExitCode(errhand.BuildDError("Unable to get index data for %s.", fk.TableIndex).AddCause(err).Build(), nil)
|
||||
}
|
||||
|
||||
parentTbl, _, ok, err := working.GetTableInsensitive(ctx, fk.ReferencedTableName)
|
||||
if err != nil {
|
||||
return HandleVErrAndExitCode(errhand.BuildDError("Unable to get table %s.", fk.ReferencedTableName).AddCause(err).Build(), nil)
|
||||
}
|
||||
if !ok {
|
||||
return HandleVErrAndExitCode(errhand.BuildDError("Table %s does not exist.", fk.ReferencedTableName).Build(), nil)
|
||||
}
|
||||
parentTblSch, err := parentTbl.GetSchema(ctx)
|
||||
if err != nil {
|
||||
return HandleVErrAndExitCode(errhand.BuildDError("Unable to get schema for %s.", fk.ReferencedTableName).AddCause(err).Build(), nil)
|
||||
}
|
||||
parentIdx := parentTblSch.Indexes().GetByName(fk.ReferencedTableIndex)
|
||||
parentIdxRowData, err := parentTbl.GetIndexRowData(ctx, fk.ReferencedTableIndex)
|
||||
if err != nil {
|
||||
return HandleVErrAndExitCode(errhand.BuildDError("Unable to get index data for %s.", fk.ReferencedTableIndex).AddCause(err).Build(), nil)
|
||||
}
|
||||
|
||||
err = table.ForeignKeyIsSatisfied(ctx, fk, childIdxRowData, parentIdxRowData, childIdx, parentIdx)
|
||||
if err != nil {
|
||||
accumulatedConstraintErrors = append(accumulatedConstraintErrors, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(accumulatedConstraintErrors) > 0 {
|
||||
dErr := errhand.BuildDError("All constraints are not satisfied.")
|
||||
dErr = dErr.AddCause(errors.New(strings.Join(accumulatedConstraintErrors, "\n")))
|
||||
return HandleVErrAndExitCode(dErr.Build(), nil)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (cmd VerifyConstraintsCmd) Hidden() bool {
|
||||
return true
|
||||
}
|
||||
@@ -84,6 +84,7 @@ var doltCommand = cli.NewSubCommandHandler("dolt", "it's git for data", []cli.Co
|
||||
commands.ReadTablesCmd{},
|
||||
commands.GarbageCollectionCmd{},
|
||||
commands.FilterBranchCmd{},
|
||||
commands.VerifyConstraintsCmd{},
|
||||
})
|
||||
|
||||
func init() {
|
||||
|
||||
Reference in New Issue
Block a user