@@ -475,6 +475,20 @@ func evalCursor(
475
475
return asOf .Timestamp , nil
476
476
}
477
477
478
+ func getTargetList (changefeedStmt * annotatedChangefeedStatement ) (* tree.BackupTargetList , error ) {
479
+ targetList := tree.BackupTargetList {}
480
+ if changefeedStmt .Level == tree .ChangefeedLevelTable {
481
+ for _ , t := range changefeedStmt .TableTargets {
482
+ targetList .Tables .TablePatterns = append (targetList .Tables .TablePatterns , t .TableName )
483
+ }
484
+ } else if changefeedStmt .Level == tree .ChangefeedLevelDatabase {
485
+ targetList .Databases = tree.NameList {tree .Name (changefeedStmt .DatabaseTarget )}
486
+ } else {
487
+ return nil , errors .AssertionFailedf ("unknown changefeed level: %s" , changefeedStmt .Level .String ())
488
+ }
489
+ return & targetList , nil
490
+ }
491
+
478
492
func createChangefeedJobRecord (
479
493
ctx context.Context ,
480
494
p sql.PlanHookState ,
@@ -542,18 +556,20 @@ func createChangefeedJobRecord(
542
556
}
543
557
}
544
558
545
- tableOnlyTargetList := tree. BackupTargetList {}
546
- for _ , t := range changefeedStmt . TableTargets {
547
- tableOnlyTargetList . Tables . TablePatterns = append ( tableOnlyTargetList . Tables . TablePatterns , t . TableName )
559
+ targetList , err := getTargetList ( changefeedStmt )
560
+ if err != nil {
561
+ return nil , err
548
562
}
549
563
550
- // This grabs table descriptors once to get their ids.
551
- targetDescs , err := getTableDescriptors (ctx , p , & tableOnlyTargetList , statementTime , initialHighWater )
564
+ var details jobspb.ChangefeedDetails
565
+
566
+ tableNameToDescriptor , targetDatabaseDescs , err := getTargetDescriptors (ctx , p , targetList , statementTime , initialHighWater )
567
+
552
568
if err != nil {
553
569
return nil , err
554
570
}
555
571
556
- for _ , t := range targetDescs {
572
+ for _ , t := range tableNameToDescriptor {
557
573
if tbl , ok := t .(catalog.TableDescriptor ); ok && tbl .ExternalRowData () != nil {
558
574
if tbl .ExternalRowData ().TenantID .IsSet () {
559
575
return nil , errors .UnimplementedError (errors.IssueLink {}, "changefeeds on a replication target are not supported" )
@@ -562,7 +578,8 @@ func createChangefeedJobRecord(
562
578
}
563
579
}
564
580
565
- targets , tables , err := getTargetsAndTables (ctx , p , targetDescs , changefeedStmt .TableTargets ,
581
+ // This grabs table descriptors once to get their ids.
582
+ targets , tables , err := getTargetsAndTables (ctx , p , tableNameToDescriptor , changefeedStmt .TableTargets ,
566
583
changefeedStmt .originalSpecs , opts .ShouldUseFullStatementTimeName ())
567
584
568
585
if err != nil {
@@ -572,19 +589,44 @@ func createChangefeedJobRecord(
572
589
sd := p .SessionData ().Clone ()
573
590
// Add non-local session data state (localization, etc).
574
591
sessiondata .MarshalNonLocal (p .SessionData (), & sd .SessionData )
575
- details := jobspb.ChangefeedDetails {
576
- Tables : tables ,
577
- SinkURI : sinkURI ,
578
- StatementTime : statementTime ,
579
- EndTime : endTime ,
580
- TargetSpecifications : targets ,
581
- SessionData : & sd .SessionData ,
592
+ if changefeedStmt .Level == tree .ChangefeedLevelTable {
593
+ for _ , t := range tableNameToDescriptor {
594
+ if tbl , ok := t .(catalog.TableDescriptor ); ok && tbl .ExternalRowData () != nil {
595
+ if tbl .ExternalRowData ().TenantID .IsSet () {
596
+ return nil , errors .UnimplementedError (errors.IssueLink {}, "changefeeds on a replication target are not supported" )
597
+ }
598
+ return nil , errors .UnimplementedError (errors.IssueLink {}, "changefeeds on external tables are not supported" )
599
+ }
600
+ }
601
+
602
+ details = jobspb.ChangefeedDetails {
603
+ Tables : tables ,
604
+ SinkURI : sinkURI ,
605
+ StatementTime : statementTime ,
606
+ EndTime : endTime ,
607
+ TargetSpecifications : targets ,
608
+ SessionData : & sd .SessionData ,
609
+ }
610
+ } else if changefeedStmt .Level == tree .ChangefeedLevelDatabase {
611
+ if len (targetDatabaseDescs ) == 0 || len (targetDatabaseDescs ) > 1 {
612
+ return nil , errors .Errorf ("only one database target is supported" )
613
+ }
614
+ targets = getDatabaseTargets (targetDatabaseDescs )
615
+ details = jobspb.ChangefeedDetails {
616
+ TargetSpecifications : targets ,
617
+ SinkURI : sinkURI ,
618
+ StatementTime : statementTime ,
619
+ EndTime : endTime ,
620
+ SessionData : & sd .SessionData ,
621
+ }
622
+ } else {
623
+ return nil , errors .AssertionFailedf ("unknown changefeed level: %s" , changefeedStmt .Level )
582
624
}
583
625
584
626
specs := AllTargets (details )
585
627
hasSelectPrivOnAllTables := true
586
628
hasChangefeedPrivOnAllTables := true
587
- for _ , desc := range targetDescs {
629
+ for _ , desc := range tableNameToDescriptor {
588
630
if table , isTable := desc .(catalog.TableDescriptor ); isTable {
589
631
if err := changefeedvalidators .ValidateTable (specs , table , tolerances ); err != nil {
590
632
return nil , err
@@ -610,7 +652,7 @@ func createChangefeedJobRecord(
610
652
if changefeedStmt .Select != nil {
611
653
// Serialize changefeed expression.
612
654
normalized , withDiff , err := validateAndNormalizeChangefeedExpression (
613
- ctx , p , opts , changefeedStmt .Select , targetDescs , targets , statementTime ,
655
+ ctx , p , opts , changefeedStmt .Select , tableNameToDescriptor , targets , statementTime ,
614
656
)
615
657
if err != nil {
616
658
return nil , err
@@ -866,7 +908,7 @@ Few hours to a few days range are appropriate values for this option.`
866
908
Description : description ,
867
909
Username : p .User (),
868
910
DescriptorIDs : func () (sqlDescIDs []descpb.ID ) {
869
- for _ , desc := range targetDescs {
911
+ for _ , desc := range tableNameToDescriptor {
870
912
sqlDescIDs = append (sqlDescIDs , desc .GetID ())
871
913
}
872
914
return sqlDescIDs
@@ -899,31 +941,31 @@ func validateSettings(ctx context.Context, needsRangeFeed bool, execCfg *sql.Exe
899
941
return nil
900
942
}
901
943
902
- func getTableDescriptors (
944
+ func getTargetDescriptors (
903
945
ctx context.Context ,
904
946
p sql.PlanHookState ,
905
947
targets * tree.BackupTargetList ,
906
948
statementTime hlc.Timestamp ,
907
949
initialHighWater hlc.Timestamp ,
908
- ) (map [tree. TablePattern ]catalog. Descriptor , error ) {
909
- // For now, disallow targeting a database or wildcard table selection.
910
- // Getting it right as tables enter and leave the set over time is
911
- // tricky.
912
- if len ( targets . Databases ) > 0 {
913
- return nil , errors . Errorf ( `CHANGEFEED cannot target %s` ,
914
- tree . AsString ( targets ) )
950
+ ) (
951
+ tableNameToDescriptor map [tree. TablePattern ]catalog. Descriptor ,
952
+ databaseDescs []catalog. DatabaseDescriptor ,
953
+ err error ,
954
+ ) {
955
+ if len ( targets . Databases ) > 0 && len ( targets . Tables . TablePatterns ) > 0 {
956
+ return nil , nil , errors . Errorf ( `CHANGEFEED cannot target both databases and tables` )
915
957
}
916
958
for _ , t := range targets .Tables .TablePatterns {
917
959
p , err := t .NormalizeTablePattern ()
918
960
if err != nil {
919
- return nil , err
961
+ return nil , nil , err
920
962
}
921
963
if _ , ok := p .(* tree.TableName ); ! ok {
922
- return nil , errors .Errorf (`CHANGEFEED cannot target %s` , tree .AsString (t ))
964
+ return nil , nil , errors .Errorf (`CHANGEFEED cannot target %s` , tree .AsString (t ))
923
965
}
924
966
}
925
967
926
- _ , _ , _ , targetDescs , err := backupresolver .ResolveTargetsToDescriptors (ctx , p , statementTime , targets )
968
+ _ , _ , targetDatabaseDescs , targetTableDescs , err := backupresolver .ResolveTargetsToDescriptors (ctx , p , statementTime , targets )
927
969
if err != nil {
928
970
var m * backupresolver.MissingTableErr
929
971
if errors .As (err , & m ) {
@@ -937,7 +979,7 @@ func getTableDescriptors(
937
979
"do the targets exist at the specified cursor time %s?" , initialHighWater )
938
980
}
939
981
}
940
- return targetDescs , err
982
+ return targetTableDescs , targetDatabaseDescs , err
941
983
}
942
984
943
985
func getTargetsAndTables (
@@ -1013,6 +1055,21 @@ func getTargetsAndTables(
1013
1055
return targets , tables , nil
1014
1056
}
1015
1057
1058
+ func getDatabaseTargets (
1059
+ targetDatabaseDescs []catalog.DatabaseDescriptor ,
1060
+ ) []jobspb.ChangefeedTargetSpecification {
1061
+ targets := make ([]jobspb.ChangefeedTargetSpecification , len (targetDatabaseDescs ))
1062
+
1063
+ for i , desc := range targetDatabaseDescs {
1064
+ targets [i ] = jobspb.ChangefeedTargetSpecification {
1065
+ DescID : desc .GetID (),
1066
+ Type : jobspb .ChangefeedTargetSpecification_DATABASE ,
1067
+ StatementTimeName : desc .GetName (),
1068
+ }
1069
+ }
1070
+ return targets
1071
+ }
1072
+
1016
1073
func validateSink (
1017
1074
ctx context.Context ,
1018
1075
p sql.PlanHookState ,
0 commit comments