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

Max/check constraint #317

Merged
merged 19 commits into from
Mar 17, 2021
Merged

Max/check constraint #317

merged 19 commits into from
Mar 17, 2021

Conversation

max-hoffman
Copy link
Contributor

@max-hoffman max-hoffman commented Feb 26, 2021

check constraints in memory engine
depends on dolthub/vitess#53

TODOs maybe:

  • in mysql, an ALTER TABLE ADD CHECK fails if any of the existing rows fail the check. This is what the current implementation does, I'm not sure if toggling that off should be supported.
  • when an INSERT row that fails a constraint, mysql errors and does not write any of the values. The current implementation defaults to INSERT IGNORE's behavior, where warnings are issues for failed checks but good rows are still included.

TODOs in future:

  • DropColumn, RenameColumn, ModifyColumn should error if touching a constraint-protected columns

@max-hoffman max-hoffman requested a review from zachmu March 2, 2021 00:03
Copy link
Member

@zachmu zachmu left a comment

Choose a reason for hiding this comment

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

Good start, but definitely need to rethink the interface for sql.CheckAlterableTable here. Few other comments as well

@@ -114,6 +114,15 @@ func createForeignKeys(t *testing.T, harness Harness, engine *sqle.Engine) {
}
}

func createCheckConstraint(t *testing.T, harness Harness, engine *sqle.Engine) {
Copy link
Member

Choose a reason for hiding this comment

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

Since you have the tests for check constraints defined as separate methods, no need to declare harness support. That's only needed for things included in queries.go and friends. Integrators can just not call the check constraint test methods if they don't support it.

memory/table.go Outdated Show resolved Hide resolved
memory/table.go Outdated Show resolved Hide resolved
memory/table.go Outdated
return nil
}

// CreateCheck implements sql.CheckAlterableTable
func (t *Table) CreateCheckConstraint(_ *sql.Context, chName string, expr sql.Expression, enforced bool) error {
Copy link
Member

Choose a reason for hiding this comment

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

Think you need to check all rows at this time, right?

sql/core.go Outdated Show resolved Hide resolved
} else if chConstraint, ok := cd.Details.(*sqlparser.CheckConstraintDefinition); ok {
var c sql.Expression
var err error
if chConstraint.Expr != nil {
Copy link
Member

Choose a reason for hiding this comment

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

This is illegal otherwise right? Should either throw an error or not check this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

DROP CHECK doesn't have an expression

sql/parse/parse_test.go Show resolved Hide resolved
}
}

// Execute inserts the rows in the database.
Copy link
Member

Choose a reason for hiding this comment

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

Typo

cols[col.Name] = true
}

sql.Inspect(p.ChDef.Expr, func(expr sql.Expression) bool {
Copy link
Member

Choose a reason for hiding this comment

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

You should do this at analyzer time, not at execution time. You basically get resolution of things like columns and functions "for free" if you make this node implement sql.Expressioner

Copy link
Member

Choose a reason for hiding this comment

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

Then after things are resolved, add a validation rule that enforces that only allowable expression types are present in the check expression

sql/plan/ddl.go Outdated
@@ -91,7 +92,7 @@ var _ sql.Node = (*CreateTable)(nil)
var _ sql.Expressioner = (*CreateTable)(nil)

// NewCreateTable creates a new CreateTable node
func NewCreateTable(db sql.Database, name string, schema sql.Schema, ifNotExists bool, idxDefs []*IndexDefinition, fkDefs []*sql.ForeignKeyConstraint) *CreateTable {
func NewCreateTable(db sql.Database, name string, schema sql.Schema, ifNotExists bool, idxDefs []*IndexDefinition, fkDefs []*sql.ForeignKeyConstraint, chDefs []*sql.CheckConstraint) *CreateTable {
Copy link
Member

Choose a reason for hiding this comment

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

This is getting a little cumbersome. I think it's time to break out a tableSpecs object with named with methods or similar.

Copy link
Member

@zachmu zachmu left a comment

Choose a reason for hiding this comment

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

Looks good, just a few remarks to add some TODOs where we need to come back for another pass

enginetest/enginetests.go Outdated Show resolved Hide resolved
enginetest/enginetests.go Show resolved Hide resolved
enginetest/harness.go Outdated Show resolved Hide resolved

// Make sure that all columns are valid, in the table, and there are no duplicates
switch expr := e.(type) {
case *expression.UnresolvedColumn:
Copy link
Member

Choose a reason for hiding this comment

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

Add a TODO here -- the columns should be resolved (or fail to resolve) before running this validation pass

Copy link
Contributor Author

Choose a reason for hiding this comment

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

non-existent fields are necessarily unresolved here, added a case for GetField in case a different table's column resolves (not sure if that's valid)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

my bad deferred*, not unresolved

sql/analyzer/check_constraints.go Outdated Show resolved Hide resolved
}
case sqlparser.DropStr:
switch c := parsedConstraint.(type) {
case *sql.ForeignKeyConstraint:
return plan.NewAlterDropForeignKey(table, c), nil
case *sql.CheckConstraint:
return plan.NewAlterDropCheck(table, c), nil
case namedConstraint:
Copy link
Member

Choose a reason for hiding this comment

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

Add a TODO here: this is broken if they try to drop a check constraint without saying it's a check constraint

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added TODO, should plan.NewAlterDropConstraint just be generalized or do we need two different plans?

@max-hoffman max-hoffman marked this pull request as ready for review March 17, 2021 19:41
@max-hoffman max-hoffman merged commit 6bf8f18 into master Mar 17, 2021
@max-hoffman max-hoffman deleted the max/check-constraint branch March 17, 2021 19:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants