Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added verify-constraints command #1106

Merged
merged 1 commit into from
Dec 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
79 changes: 79 additions & 0 deletions bats/verify-constraints.bats
Original file line number Diff line number Diff line change
@@ -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 changes: 134 additions & 0 deletions go/cmd/dolt/commands/verify_constraints.go
Original file line number Diff line number Diff line change
@@ -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())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this error message include the name of the table with the failing constraint? If not, add it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, errors look like:

foreign key violation on `child1_parent2`.`child1`: `(15292,2)`

}
}
}

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
}
1 change: 1 addition & 0 deletions go/cmd/dolt/dolt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down