diff --git a/sql/backfill.go b/sql/backfill.go index 3cef64562093..ac703222d3b3 100644 --- a/sql/backfill.go +++ b/sql/backfill.go @@ -392,21 +392,15 @@ func (sc *SchemaChanger) truncateIndexes( return nil } - indexPrefix := sqlbase.MakeIndexKeyPrefix(tableDesc, desc.ID) - - // Delete the index. - indexStartKey := roachpb.Key(indexPrefix) - indexEndKey := indexStartKey.PrefixEnd() - if log.V(2) { - log.Infof(context.TODO(), "DelRange %s - %s", indexStartKey, indexEndKey) + rd, err := makeRowDeleter(txn, tableDesc, nil, nil, false) + if err != nil { + return err } - b := &client.Batch{} - b.DelRange(indexStartKey, indexEndKey, false) - - if err := txn.Run(b); err != nil { + td := tableDeleter{rd: rd} + if err := td.init(txn); err != nil { return err } - return nil + return td.deleteIndex(&desc) }); err != nil { return err } diff --git a/sql/create.go b/sql/create.go index 86dffd03a152..93e3a1b00b63 100644 --- a/sql/create.go +++ b/sql/create.go @@ -605,9 +605,15 @@ func (p *planner) finalizeInterleave( } // Only the last ancestor needs the backreference. ancestor := index.Interleave.Ancestors[len(index.Interleave.Ancestors)-1] - ancestorTable, err := sqlbase.GetTableDescFromID(p.txn, ancestor.TableID) - if err != nil { - return err + var ancestorTable *sqlbase.TableDescriptor + if ancestor.TableID == desc.ID { + ancestorTable = desc + } else { + var err error + ancestorTable, err = sqlbase.GetTableDescFromID(p.txn, ancestor.TableID) + if err != nil { + return err + } } ancestorIndex, err := ancestorTable.FindIndexByID(ancestor.IndexID) if err != nil { diff --git a/sql/drop.go b/sql/drop.go index 0f9cff46e4ea..728ec8a1dd98 100644 --- a/sql/drop.go +++ b/sql/drop.go @@ -27,6 +27,7 @@ import ( "github.com/cockroachdb/cockroach/sql/parser" "github.com/cockroachdb/cockroach/sql/privilege" "github.com/cockroachdb/cockroach/sql/sqlbase" + "github.com/cockroachdb/cockroach/util" "github.com/cockroachdb/cockroach/util/log" "github.com/pkg/errors" ) @@ -222,6 +223,11 @@ func (n *dropIndexNode) Start() error { return err } } + if len(idx.Interleave.Ancestors) > 0 { + if err := n.p.removeInterleaveBackReference(tableDesc, idx); err != nil { + return err + } + } for _, ref := range idx.ReferencedBy { fetched, err := n.p.canRemoveFK(idx.Name, ref, n.n.DropBehavior) @@ -232,6 +238,11 @@ func (n *dropIndexNode) Start() error { return err } } + for _, ref := range idx.InterleavedBy { + if err := n.p.removeInterleave(ref); err != nil { + return err + } + } tableDesc.AddIndexMutation(tableDesc.Indexes[i], sqlbase.DescriptorMutation_DROP) tableDesc.Indexes = append(tableDesc.Indexes[:i], tableDesc.Indexes[i+1:]...) @@ -320,6 +331,11 @@ func (p *planner) DropTable(n *parser.DropTable) (planNode, error) { return nil, err } } + for _, ref := range idx.InterleavedBy { + if err := p.canRemoveInterleave(droppedDesc.Name, ref, n.DropBehavior); err != nil { + return nil, err + } + } } td = append(td, droppedDesc) } @@ -350,6 +366,29 @@ func (p *planner) canRemoveFK( return table, nil } +func (p *planner) canRemoveInterleave( + from string, ref sqlbase.ForeignKeyReference, behavior parser.DropBehavior, +) error { + table, err := sqlbase.GetTableDescFromID(p.txn, ref.Table) + if err != nil { + return err + } + // TODO(dan): It's possible to DROP a table that has a child interleave, but + // some loose ends would have to be addresssed. The zone would have to be + // kept and deleted when the last table in it is removed. Also, the dropped + // table's descriptor would have to be kept around in some Dropped but + // non-public state for referential integrity of the `InterleaveDescriptor` + // pointers. + if behavior != parser.DropCascade { + return util.UnimplementedWithIssueErrorf( + 8036, "%q is interleaved by table %q", from, table.Name) + } + if err := p.checkPrivilege(table, privilege.CREATE); err != nil { + return err + } + return nil +} + func (p *planner) removeFK(ref *sqlbase.ForeignKeyReference, table *sqlbase.TableDescriptor) error { if table == nil { var err error @@ -366,6 +405,19 @@ func (p *planner) removeFK(ref *sqlbase.ForeignKeyReference, table *sqlbase.Tabl return p.saveNonmutationAndNotify(table) } +func (p *planner) removeInterleave(ref sqlbase.ForeignKeyReference) error { + table, err := sqlbase.GetTableDescFromID(p.txn, ref.Table) + if err != nil { + return err + } + idx, err := table.FindIndexByID(ref.Index) + if err != nil { + return err + } + idx.Interleave.Ancestors = nil + return p.saveNonmutationAndNotify(table) +} + func (n *dropTableNode) Start() error { for _, droppedDesc := range n.td { if droppedDesc == nil { @@ -443,19 +495,29 @@ func (p *planner) dropTableImpl(tableDesc *sqlbase.TableDescriptor) error { } p.notifySchemaChange(tableDesc.ID, sqlbase.InvalidMutationID) - // Remove FK relationships. + // Remove FK and interleave relationships. for _, idx := range tableDesc.AllNonDropIndexes() { if idx.ForeignKey != nil { if err := p.removeFKBackReference(tableDesc, idx); err != nil { return err } } + if len(idx.Interleave.Ancestors) > 0 { + if err := p.removeInterleaveBackReference(tableDesc, idx); err != nil { + return err + } + } for _, ref := range idx.ReferencedBy { // Nil forces re-fetching tables, since they may have been modified. if err := p.removeFK(ref, nil); err != nil { return err } } + for _, ref := range idx.InterleavedBy { + if err := p.removeInterleave(ref); err != nil { + return err + } + } } verifyMetadataCallback := func(systemConfig config.SystemConfig, tableID sqlbase.ID) error { @@ -497,6 +559,29 @@ func (p *planner) removeFKBackReference( return p.saveNonmutationAndNotify(t) } +func (p *planner) removeInterleaveBackReference( + tableDesc *sqlbase.TableDescriptor, idx sqlbase.IndexDescriptor, +) error { + if len(idx.Interleave.Ancestors) == 0 { + return nil + } + ancestor := idx.Interleave.Ancestors[len(idx.Interleave.Ancestors)-1] + t, err := sqlbase.GetTableDescFromID(p.txn, ancestor.TableID) + if err != nil { + return errors.Errorf("error resolving referenced table ID %d: %v", ancestor.TableID, err) + } + targetIdx, err := t.FindIndexByID(ancestor.IndexID) + if err != nil { + return err + } + for k, ref := range targetIdx.InterleavedBy { + if ref.Table == tableDesc.ID && ref.Index == idx.ID { + targetIdx.InterleavedBy = append(targetIdx.InterleavedBy[:k], targetIdx.InterleavedBy[k+1:]...) + } + } + return p.saveNonmutationAndNotify(t) +} + // truncateAndDropTable batches all the commands required for truncating and deleting the // table descriptor. // It is called from a mutation, async wrt the DROP statement. diff --git a/sql/rowwriter.go b/sql/rowwriter.go index 067d96af2aae..ba32a2c4ce8e 100644 --- a/sql/rowwriter.go +++ b/sql/rowwriter.go @@ -835,6 +835,26 @@ func (rd *rowDeleter) deleteRow(b *client.Batch, values []parser.Datum) error { return nil } +// deleteIndexRow adds to the batch the kv operations necessary to delete a +// table row from the given index. +func (rd *rowDeleter) deleteIndexRow( + b *client.Batch, idx *sqlbase.IndexDescriptor, values []parser.Datum, +) error { + if err := rd.fks.checkAll(values); err != nil { + return err + } + secondaryIndexEntry, err := sqlbase.EncodeSecondaryIndex( + rd.helper.tableDesc, idx, rd.fetchColIDtoRowIndex, values) + if err != nil { + return err + } + if log.V(2) { + log.Infof(context.TODO(), "Del %s", secondaryIndexEntry.Key) + } + b.Del(secondaryIndexEntry.Key) + return nil +} + func colIDtoRowIndexFromCols(cols []sqlbase.ColumnDescriptor) map[sqlbase.ColumnID]int { colIDtoRowIndex := make(map[sqlbase.ColumnID]int, len(cols)) for i, col := range cols { diff --git a/sql/tablewriter.go b/sql/tablewriter.go index b52aae236446..218d032d8f4b 100644 --- a/sql/tablewriter.go +++ b/sql/tablewriter.go @@ -26,6 +26,7 @@ import ( "github.com/cockroachdb/cockroach/roachpb" "github.com/cockroachdb/cockroach/sql/parser" "github.com/cockroachdb/cockroach/sql/sqlbase" + "github.com/cockroachdb/cockroach/util/encoding" "github.com/cockroachdb/cockroach/util/log" "github.com/pkg/errors" ) @@ -578,3 +579,133 @@ func (td *tableDeleter) fastDelete( td.b = nil return rowCount, nil } + +// deleteAllRows runs the kv operations necessary to delete all sql rows in the +// table passed at construction. This may require a scan. +func (td *tableDeleter) deleteAllRows() error { + if td.rd.helper.tableDesc.IsInterleaved() { + if log.V(2) { + log.Info(context.TODO(), "delete forced to scan: table is interleaved") + } + return td.deleteAllRowsScan() + } + return td.deleteAllRowsFast() +} + +func (td *tableDeleter) deleteAllRowsFast() error { + var tablePrefix []byte + // TODO(dan): This should be moved into keys.MakeTablePrefix, but updating + // all the uses of that will be a pain. + if interleave := td.rd.helper.tableDesc.PrimaryIndex.Interleave; len(interleave.Ancestors) > 0 { + tablePrefix = encoding.EncodeUvarintAscending(nil, uint64(interleave.Ancestors[0].TableID)) + } + tablePrefix = encoding.EncodeUvarintAscending(nil, uint64(td.rd.helper.tableDesc.ID)) + + // Delete rows and indexes starting with the table's prefix. + tableStartKey := roachpb.Key(tablePrefix) + tableEndKey := tableStartKey.PrefixEnd() + if log.V(2) { + log.Infof(context.TODO(), "DelRange %s - %s", tableStartKey, tableEndKey) + } + td.b.DelRange(tableStartKey, tableEndKey, false) + return td.finalize() +} + +func (td *tableDeleter) deleteAllRowsScan() error { + tablePrefix := sqlbase.MakeIndexKeyPrefix( + td.rd.helper.tableDesc, td.rd.helper.tableDesc.PrimaryIndex.ID) + span := sqlbase.Span{Start: roachpb.Key(tablePrefix), End: roachpb.Key(tablePrefix).PrefixEnd()} + + valNeededForCol := make([]bool, len(td.rd.helper.tableDesc.Columns)) + for _, idx := range td.rd.fetchColIDtoRowIndex { + valNeededForCol[idx] = true + } + + var rf sqlbase.RowFetcher + err := rf.Init( + td.rd.helper.tableDesc, td.rd.fetchColIDtoRowIndex, &td.rd.helper.tableDesc.PrimaryIndex, + false, false, td.rd.fetchCols, valNeededForCol) + if err != nil { + return err + } + if err := rf.StartScan(td.txn, sqlbase.Spans{span}, 0); err != nil { + return err + } + + for { + row, err := rf.NextRow() + if err != nil { + return err + } + if row == nil { + // Done deleting rows. + break + } + _, err = td.row(row) + if err != nil { + return err + } + } + return td.finalize() +} + +// deleteIndex runs the kv operations necessary to delete all kv entries in the +// given index. This may require a scan. +func (td *tableDeleter) deleteIndex(idx *sqlbase.IndexDescriptor) error { + if len(idx.Interleave.Ancestors) > 0 || len(idx.InterleavedBy) > 0 { + if log.V(2) { + log.Info(context.TODO(), "delete forced to scan: table is interleaved") + } + return td.deleteIndexScan(idx) + } + return td.deleteIndexFast(idx) +} + +func (td *tableDeleter) deleteIndexFast(idx *sqlbase.IndexDescriptor) error { + indexPrefix := sqlbase.MakeIndexKeyPrefix(td.rd.helper.tableDesc, idx.ID) + indexStartKey := roachpb.Key(indexPrefix) + indexEndKey := indexStartKey.PrefixEnd() + + if log.V(2) { + log.Infof(context.TODO(), "DelRange %s - %s", indexStartKey, indexEndKey) + } + td.b.DelRange(indexStartKey, indexEndKey, false) + return td.finalize() +} + +func (td *tableDeleter) deleteIndexScan(idx *sqlbase.IndexDescriptor) error { + tablePrefix := sqlbase.MakeIndexKeyPrefix( + td.rd.helper.tableDesc, td.rd.helper.tableDesc.PrimaryIndex.ID) + span := sqlbase.Span{Start: roachpb.Key(tablePrefix), End: roachpb.Key(tablePrefix).PrefixEnd()} + + valNeededForCol := make([]bool, len(td.rd.helper.tableDesc.Columns)) + for _, idx := range td.rd.fetchColIDtoRowIndex { + valNeededForCol[idx] = true + } + + var rf sqlbase.RowFetcher + err := rf.Init( + td.rd.helper.tableDesc, td.rd.fetchColIDtoRowIndex, &td.rd.helper.tableDesc.PrimaryIndex, + false, false, td.rd.fetchCols, valNeededForCol) + if err != nil { + return err + } + if err := rf.StartScan(td.txn, sqlbase.Spans{span}, 0); err != nil { + return err + } + + for { + row, err := rf.NextRow() + if err != nil { + return err + } + if row == nil { + // Done deleting rows. + break + } + if err := td.rd.deleteIndexRow(td.b, idx, row); err != nil { + return err + } + } + return td.finalize() +} diff --git a/sql/testdata/interleaved b/sql/testdata/interleaved index 17ca1dfbf266..75843872b241 100644 --- a/sql/testdata/interleaved +++ b/sql/testdata/interleaved @@ -142,6 +142,45 @@ SELECT * FROM p0 3 3 3.0 3 5 5 5.0 5 +statement ok +DROP INDEX p0@p0i + +query ITTT +SELECT * FROM p0 +---- +2 2 2.0 2 +3 3 3.0 3 +5 5 5.0 5 + +statement ok +DROP TABLE p0 + +query ITTT +SELECT * FROM p1_0 +---- +2 2 2.01 2 +7 5 7.01 7 + +statement ok +TRUNCATE TABLE p2 + +statement error unimplemented +DROP TABLE p2 + +statement ok +CREATE INDEX p1_s2 ON p1_1 (s2) + +query ITTT +SELECT * FROM p1_0 +---- +2 2 2.01 2 +7 5 7.01 7 + +statement ok +DROP TABLE p2 CASCADE + +statement error table "p0" does not exist +SELECT * FROM p0 # Validation and descriptor bookkeeping @@ -153,52 +192,52 @@ CREATE TABLE all_interleaves ( d INT, INDEX (c), UNIQUE INDEX (d) -) INTERLEAVE IN PARENT p2 (b) +) INTERLEAVE IN PARENT p1_1 (b) statement ok -CREATE INDEX ON all_interleaves (c, d) INTERLEAVE IN PARENT p2 (c) +CREATE INDEX ON all_interleaves (c, d) INTERLEAVE IN PARENT p1_1 (c) statement ok -CREATE UNIQUE INDEX ON all_interleaves (d, c) INTERLEAVE IN PARENT p2 (d) +CREATE UNIQUE INDEX ON all_interleaves (d, c) INTERLEAVE IN PARENT p1_1 (d) query TT SHOW CREATE TABLE all_interleaves ---- all_interleaves CREATE TABLE all_interleaves ( - b INT NOT NULL, - c INT NULL, - d INT NULL, - CONSTRAINT "primary" PRIMARY KEY (b), - INDEX all_interleaves_c_idx (c), - UNIQUE INDEX all_interleaves_d_key (d), - INDEX all_interleaves_c_d_idx (c, d) INTERLEAVE IN PARENT p2 (c), - UNIQUE INDEX all_interleaves_d_c_key (d, c) INTERLEAVE IN PARENT p2 (d), - FAMILY "primary" (b, c, d) - ) INTERLEAVE IN PARENT p2 (b) + b INT NOT NULL, + c INT NULL, + d INT NULL, + CONSTRAINT "primary" PRIMARY KEY (b), + INDEX all_interleaves_c_idx (c), + UNIQUE INDEX all_interleaves_d_key (d), + INDEX all_interleaves_c_d_idx (c, d) INTERLEAVE IN PARENT p1_1 (c), + UNIQUE INDEX all_interleaves_d_c_key (d, c) INTERLEAVE IN PARENT p1_1 (d), + FAMILY "primary" (b, c, d) + ) INTERLEAVE IN PARENT p1_1 (b) statement error table \"missing\" does not exist CREATE TABLE err (f FLOAT PRIMARY KEY) INTERLEAVE IN PARENT missing (f) statement error interleaved columns must match parent -CREATE TABLE err (f FLOAT PRIMARY KEY) INTERLEAVE IN PARENT p2 (f) +CREATE TABLE err (f FLOAT PRIMARY KEY) INTERLEAVE IN PARENT p1_1 (f) statement error interleaved columns must match parent -CREATE INDEX ON p0 (i DESC) INTERLEAVE IN PARENT p2 (i) +CREATE INDEX ON p1_0 (i DESC) INTERLEAVE IN PARENT p1_1 (i) statement error interleaved columns must match parent -CREATE INDEX ON p0 (d) INTERLEAVE IN PARENT p2 (d) +CREATE INDEX ON p1_0 (d) INTERLEAVE IN PARENT p1_1 (d) statement error declared columns must match index being interleaved -CREATE TABLE err (i INT, j INT, PRIMARY KEY (i, j)) INTERLEAVE IN PARENT p2 (j) +CREATE TABLE err (i INT, j INT, PRIMARY KEY (i, j)) INTERLEAVE IN PARENT p1_1 (j) statement error unimplemented -CREATE TABLE err (i INT PRIMARY KEY, INDEX (i) INTERLEAVE IN PARENT p2 (i)) +CREATE TABLE err (i INT PRIMARY KEY, INDEX (i) INTERLEAVE IN PARENT p1_1 (i)) statement error unimplemented -CREATE TABLE err (i INT PRIMARY KEY, UNIQUE INDEX (i) INTERLEAVE IN PARENT p2 (i)) +CREATE TABLE err (i INT PRIMARY KEY, UNIQUE INDEX (i) INTERLEAVE IN PARENT p1_1 (i)) statement error unimplemented: unsupported shorthand CASCADE -CREATE TABLE err (i INT PRIMARY KEY) INTERLEAVE IN PARENT p2 (i) CASCADE +CREATE TABLE err (i INT PRIMARY KEY) INTERLEAVE IN PARENT p1_1 (i) CASCADE statement error unimplemented: unsupported shorthand RESTRICT -CREATE TABLE err (i INT PRIMARY KEY) INTERLEAVE IN PARENT p2 (i) RESTRICT +CREATE TABLE err (i INT PRIMARY KEY) INTERLEAVE IN PARENT p1_1 (i) RESTRICT diff --git a/sql/truncate.go b/sql/truncate.go index dcf04044bce6..435556659009 100644 --- a/sql/truncate.go +++ b/sql/truncate.go @@ -18,13 +18,9 @@ package sql import ( "github.com/cockroachdb/cockroach/internal/client" - "github.com/cockroachdb/cockroach/keys" - "github.com/cockroachdb/cockroach/roachpb" "github.com/cockroachdb/cockroach/sql/parser" "github.com/cockroachdb/cockroach/sql/privilege" "github.com/cockroachdb/cockroach/sql/sqlbase" - "github.com/cockroachdb/cockroach/util/log" - "golang.org/x/net/context" ) // Truncate deletes all rows from a table. @@ -65,15 +61,13 @@ func (p *planner) Truncate(n *parser.Truncate) (planNode, error) { // It deletes a range of data for the table, which includes the PK and all // indexes. func truncateTable(tableDesc *sqlbase.TableDescriptor, txn *client.Txn) error { - tablePrefix := keys.MakeTablePrefix(uint32(tableDesc.ID)) - - // Delete rows and indexes starting with the table's prefix. - tableStartKey := roachpb.Key(tablePrefix) - tableEndKey := tableStartKey.PrefixEnd() - if log.V(2) { - log.Infof(context.TODO(), "DelRange %s - %s", tableStartKey, tableEndKey) + rd, err := makeRowDeleter(txn, tableDesc, nil, nil, false) + if err != nil { + return err + } + td := tableDeleter{rd: rd} + if err := td.init(txn); err != nil { + return err } - b := client.Batch{} - b.DelRange(tableStartKey, tableEndKey, false) - return txn.Run(&b) + return td.deleteAllRows() }