diff --git a/pkg/sql/catalog/descpb/structured.go b/pkg/sql/catalog/descpb/structured.go index e89f0371cef0..6b504bd12e58 100644 --- a/pkg/sql/catalog/descpb/structured.go +++ b/pkg/sql/catalog/descpb/structured.go @@ -230,6 +230,11 @@ func (desc *TableDescriptor) MaterializedView() bool { return desc.IsMaterializedView } +// IsReadOnly implements the TableDescriptor interface. +func (desc *TableDescriptor) IsReadOnly() bool { + return desc.IsMaterializedView || desc.GetExternal() != nil +} + // IsPhysicalTable implements the TableDescriptor interface. func (desc *TableDescriptor) IsPhysicalTable() bool { return desc.IsSequence() || (desc.IsTable() && !desc.IsVirtualTable()) || desc.MaterializedView() diff --git a/pkg/sql/catalog/descriptor.go b/pkg/sql/catalog/descriptor.go index 20dab3c12669..0255dcfa46a6 100644 --- a/pkg/sql/catalog/descriptor.go +++ b/pkg/sql/catalog/descriptor.go @@ -337,6 +337,9 @@ type TableDescriptor interface { IsPhysicalTable() bool // MaterializedView returns whether this TableDescriptor is a MaterializedView. MaterializedView() bool + // IsReadOnly returns if this table descriptor has external data, and cannot + // be written to. + IsReadOnly() bool // IsAs returns true if the TableDescriptor describes a Table that was created // with a CREATE TABLE AS command. IsAs() bool diff --git a/pkg/sql/catalog/replication/reader_catalog_test.go b/pkg/sql/catalog/replication/reader_catalog_test.go index 5f5b857779dc..6f45fbed90ca 100644 --- a/pkg/sql/catalog/replication/reader_catalog_test.go +++ b/pkg/sql/catalog/replication/reader_catalog_test.go @@ -92,6 +92,12 @@ func TestReaderCatalog(t *testing.T) { compareConn("SELECT * FROM system.role_options") compareConn("SELECT * FROM system.database_role_settings") + // Validate that sequences can be selected. + compareConn("SELECT * FROM sq1") + + // Validate that sequence operations are blocked. + destRunner.ExpectErr(t, "cannot execute nextval\\(\\) in a read-only transaction", "SELECT nextval('sq1')") + destRunner.ExpectErr(t, "cannot execute setval\\(\\) in a read-only transaction", "SELECT setval('sq1', 32)") // Manipulate the schema first. ddlToExec = []string{ "ALTER TABLE t1 ADD COLUMN j int default 32", diff --git a/pkg/sql/sequence.go b/pkg/sql/sequence.go index 5c244fbc93db..550eab5eaadd 100644 --- a/pkg/sql/sequence.go +++ b/pkg/sql/sequence.go @@ -112,6 +112,11 @@ func incrementSequenceHelper( string(descriptor.DescriptorType()), descriptor.GetName()) } + // If the descriptor is read only block any write operations. + if descriptor.IsReadOnly() { + return 0, readOnlyError("nextval()") + } + var err error seqOpts := descriptor.GetSequenceOpts() @@ -294,6 +299,10 @@ func (p *planner) SetSequenceValueByID( if err != nil { return err } + // If the descriptor is read only block any write operations. + if descriptor.IsReadOnly() { + return readOnlyError("setval()") + } seqName, err := p.getQualifiedTableName(ctx, descriptor) if err != nil { return err @@ -410,7 +419,14 @@ func (p *planner) GetSequenceValue( func getSequenceValueFromDesc( ctx context.Context, txn *kv.Txn, codec keys.SQLCodec, desc catalog.TableDescriptor, ) (int64, error) { - keyValue, err := txn.Get(ctx, codec.SequenceKey(uint32(desc.GetID()))) + targetID := desc.GetID() + // For external row data, adjust the key that we will + // scan. + if ext := desc.ExternalRowData(); ext != nil { + codec = keys.MakeSQLCodec(ext.TenantID) + targetID = desc.ExternalRowData().TableID + } + keyValue, err := txn.Get(ctx, codec.SequenceKey(uint32(targetID))) if err != nil { return 0, err }