Skip to content

Commit 630d751

Browse files
authored
feat(bigtable): Add support for logical views (#11792)
* feat(bigtable): Add support for logical views * fix build * fixed according to PR comments
1 parent f47e038 commit 630d751

2 files changed

Lines changed: 230 additions & 0 deletions

File tree

bigtable/admin.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ func (ac *AdminClient) authorizedViewPath(table, authorizedView string) string {
130130
return fmt.Sprintf("%s/tables/%s/authorizedViews/%s", ac.instancePrefix(), table, authorizedView)
131131
}
132132

133+
func logicalViewPath(project, instance, logicalView string) string {
134+
return fmt.Sprintf("%s/logicalViews/%s", instancePrefix(project, instance), logicalView)
135+
}
136+
133137
// EncryptionInfo represents the encryption info of a table.
134138
type EncryptionInfo struct {
135139
Status *Status
@@ -2604,6 +2608,8 @@ func (s *SubsetViewConf) AddFamilySubsetQualifierPrefix(familyName string, quali
26042608
s.FamilySubsets[familyName] = fs
26052609
}
26062610

2611+
// Authorized Views
2612+
26072613
// CreateAuthorizedView creates a new authorized view in a table.
26082614
func (ac *AdminClient) CreateAuthorizedView(ctx context.Context, conf *AuthorizedViewConf) error {
26092615
if conf.TableID == "" || conf.AuthorizedViewID == "" {
@@ -2765,3 +2771,115 @@ func (ac *AdminClient) DeleteAuthorizedView(ctx context.Context, tableID, author
27652771
_, err := ac.tClient.DeleteAuthorizedView(ctx, req)
27662772
return err
27672773
}
2774+
2775+
// Logical Views
2776+
2777+
// CreateLogicalView creates a new logical view in an instance.
2778+
func (iac *InstanceAdminClient) CreateLogicalView(ctx context.Context, instanceID string, conf *LogicalViewInfo) error {
2779+
if conf.LogicalViewID == "" {
2780+
return errors.New("LogicalViewID is required")
2781+
}
2782+
2783+
ctx = mergeOutgoingMetadata(ctx, iac.md)
2784+
req := &btapb.CreateLogicalViewRequest{
2785+
Parent: instancePrefix(iac.project, instanceID),
2786+
LogicalViewId: conf.LogicalViewID,
2787+
LogicalView: &btapb.LogicalView{
2788+
Query: conf.Query,
2789+
},
2790+
}
2791+
_, err := iac.iClient.CreateLogicalView(ctx, req)
2792+
return err
2793+
}
2794+
2795+
// LogicalViewInfo contains logical view metadata. This struct is read-only.
2796+
type LogicalViewInfo struct {
2797+
LogicalViewID string
2798+
2799+
Query string
2800+
}
2801+
2802+
// LogicalViewInfo retrieves information about a logical view.
2803+
func (iac *InstanceAdminClient) LogicalViewInfo(ctx context.Context, instanceID, logicalViewID string) (*LogicalViewInfo, error) {
2804+
ctx = mergeOutgoingMetadata(ctx, iac.md)
2805+
prefix := instancePrefix(iac.project, instanceID)
2806+
req := &btapb.GetLogicalViewRequest{
2807+
Name: logicalViewPath(iac.project, instanceID, logicalViewID),
2808+
}
2809+
var res *btapb.LogicalView
2810+
2811+
err := gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error {
2812+
var err error
2813+
res, err = iac.iClient.GetLogicalView(ctx, req)
2814+
return err
2815+
}, retryOptions...)
2816+
2817+
if err != nil {
2818+
return nil, err
2819+
}
2820+
return &LogicalViewInfo{LogicalViewID: strings.TrimPrefix(res.Name, prefix+"/logicalViews/"), Query: res.Query}, nil
2821+
}
2822+
2823+
// LogicalViews returns a list of the logical views in the instance.
2824+
func (iac *InstanceAdminClient) LogicalViews(ctx context.Context, instanceID string) ([]LogicalViewInfo, error) {
2825+
views := []LogicalViewInfo{}
2826+
prefix := instancePrefix(iac.project, instanceID)
2827+
req := &btapb.ListLogicalViewsRequest{
2828+
Parent: prefix,
2829+
}
2830+
var res *btapb.ListLogicalViewsResponse
2831+
err := gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error {
2832+
var err error
2833+
res, err = iac.iClient.ListLogicalViews(ctx, req)
2834+
return err
2835+
}, retryOptions...)
2836+
if err != nil {
2837+
return nil, err
2838+
}
2839+
2840+
for _, lv := range res.LogicalViews {
2841+
views = append(views, LogicalViewInfo{LogicalViewID: strings.TrimPrefix(lv.Name, prefix+"/logicalViews/"), Query: lv.Query})
2842+
}
2843+
return views, nil
2844+
}
2845+
2846+
// UpdateLogicalView updates a logical view in an instance according to the given configuration.
2847+
func (iac *InstanceAdminClient) UpdateLogicalView(ctx context.Context, instanceID string, conf LogicalViewInfo) error {
2848+
ctx = mergeOutgoingMetadata(ctx, iac.md)
2849+
if conf.LogicalViewID == "" {
2850+
return errors.New("LogicalViewID is required")
2851+
}
2852+
lv := &btapb.LogicalView{}
2853+
lv.Name = logicalViewPath(iac.project, instanceID, conf.LogicalViewID)
2854+
2855+
updateMask := &field_mask.FieldMask{
2856+
Paths: []string{},
2857+
}
2858+
if conf.Query != "" {
2859+
updateMask.Paths = append(updateMask.Paths, "query")
2860+
}
2861+
req := &btapb.UpdateLogicalViewRequest{
2862+
LogicalView: lv,
2863+
UpdateMask: updateMask,
2864+
}
2865+
lro, err := iac.iClient.UpdateLogicalView(ctx, req)
2866+
if err != nil {
2867+
return fmt.Errorf("error from update logical view: %w", err)
2868+
}
2869+
var res btapb.LogicalView
2870+
op := longrunning.InternalNewOperation(iac.lroClient, lro)
2871+
if err = op.Wait(ctx, &res); err != nil {
2872+
return fmt.Errorf("error from operation: %v", err)
2873+
}
2874+
return nil
2875+
}
2876+
2877+
// DeleteLogicalView deletes a logical view in an instance.
2878+
func (iac *InstanceAdminClient) DeleteLogicalView(ctx context.Context, instanceID, logicalViewID string) error {
2879+
ctx = mergeOutgoingMetadata(ctx, iac.md)
2880+
req := &btapb.DeleteLogicalViewRequest{
2881+
Name: logicalViewPath(iac.project, instanceID, logicalViewID),
2882+
}
2883+
_, err := iac.iClient.DeleteLogicalView(ctx, req)
2884+
return err
2885+
}

bigtable/integration_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4330,6 +4330,118 @@ func TestIntegration_DataAuthorizedView(t *testing.T) {
43304330
}
43314331
}
43324332

4333+
func TestIntegration_AdminLogicalView(t *testing.T) {
4334+
testEnv, err := NewIntegrationEnv()
4335+
if err != nil {
4336+
t.Fatalf("IntegrationEnv: %v", err)
4337+
}
4338+
defer testEnv.Close()
4339+
4340+
if !testEnv.Config().UseProd {
4341+
t.Skip("emulator doesn't support logicalViews")
4342+
}
4343+
4344+
timeout := 15 * time.Minute
4345+
ctx, cancel := context.WithTimeout(context.Background(), timeout)
4346+
defer cancel()
4347+
4348+
adminClient, err := testEnv.NewAdminClient()
4349+
if err != nil {
4350+
t.Fatalf("NewAdminClient: %v", err)
4351+
}
4352+
defer adminClient.Close()
4353+
4354+
instanceAdminClient, err := testEnv.NewInstanceAdminClient()
4355+
if err != nil {
4356+
t.Fatalf("NewInstanceAdminClient: %v", err)
4357+
}
4358+
defer instanceAdminClient.Close()
4359+
4360+
tblConf := TableConf{
4361+
TableID: testEnv.Config().Table,
4362+
Families: map[string]GCPolicy{
4363+
"fam1": MaxVersionsPolicy(1),
4364+
"fam2": MaxVersionsPolicy(2),
4365+
},
4366+
}
4367+
if err := createTableFromConf(ctx, adminClient, &tblConf); err != nil {
4368+
t.Fatalf("Creating table from TableConf: %v", err)
4369+
}
4370+
// Delete the table at the end of the test. Schedule ahead of time
4371+
// in case the client fails
4372+
defer deleteTable(ctx, t, adminClient, tblConf.TableID)
4373+
4374+
// Create logical view
4375+
logicalViewUUID := uid.NewSpace("logicalView-", &uid.Options{})
4376+
logicalView := logicalViewUUID.New()
4377+
defer instanceAdminClient.DeleteLogicalView(ctx, testEnv.Config().Instance, logicalView)
4378+
4379+
logicalViewInfo := LogicalViewInfo{
4380+
LogicalViewID: logicalView,
4381+
Query: fmt.Sprintf("SELECT _key, fam1[col1] as col FROM %s", tblConf.TableID),
4382+
}
4383+
if err = instanceAdminClient.CreateLogicalView(ctx, testEnv.Config().Instance, &logicalViewInfo); err != nil {
4384+
t.Fatalf("Creating logical view: %v", err)
4385+
}
4386+
4387+
// List logical views
4388+
logicalViews, err := instanceAdminClient.LogicalViews(ctx, testEnv.Config().Instance)
4389+
if err != nil {
4390+
t.Fatalf("Listing logical views: %v", err)
4391+
}
4392+
if got, want := len(logicalViews), 1; got != want {
4393+
t.Fatalf("Listing logical views count: %d, want: != %d", got, want)
4394+
}
4395+
if got, want := logicalViews[0].LogicalViewID, logicalView; got != want {
4396+
t.Errorf("LogicalView Name: %s, want: %s", got, want)
4397+
}
4398+
if got, want := logicalViews[0].Query, logicalViewInfo.Query; got != want {
4399+
t.Errorf("LogicalView Query: %q, want: %q", got, want)
4400+
}
4401+
4402+
// Get logical view
4403+
lvInfo, err := instanceAdminClient.LogicalViewInfo(ctx, testEnv.Config().Instance, logicalView)
4404+
if err != nil {
4405+
t.Fatalf("Getting logical view: %v", err)
4406+
}
4407+
if got, want := lvInfo.Query, logicalViewInfo.Query; got != want {
4408+
t.Errorf("LogicalView Query: %q, want: %q", got, want)
4409+
}
4410+
4411+
// Update logical view
4412+
newLogicalViewInfo := LogicalViewInfo{
4413+
LogicalViewID: logicalView,
4414+
Query: fmt.Sprintf("SELECT _key, fam2[col1] as col FROM %s", tblConf.TableID),
4415+
}
4416+
err = instanceAdminClient.UpdateLogicalView(ctx, testEnv.Config().Instance, newLogicalViewInfo)
4417+
if err != nil {
4418+
t.Fatalf("UpdateLogicalView failed: %v", err)
4419+
}
4420+
4421+
// Check that updated logical view has the correct deletion protection
4422+
lvInfo, err = instanceAdminClient.LogicalViewInfo(ctx, testEnv.Config().Instance, logicalView)
4423+
if err != nil {
4424+
t.Fatalf("Getting logical view: %v", err)
4425+
}
4426+
if got, want := lvInfo.Query, newLogicalViewInfo.Query; got != want {
4427+
t.Errorf("LogicalView Query: %q, want: %q", got, want)
4428+
}
4429+
4430+
// Delete logical view
4431+
if err = instanceAdminClient.DeleteLogicalView(ctx, testEnv.Config().Instance, logicalView); err != nil {
4432+
t.Fatalf("DeleteLogicalView: %v", err)
4433+
}
4434+
4435+
// Verify the logical view was deleted.
4436+
logicalViews, err = instanceAdminClient.LogicalViews(ctx, testEnv.Config().Instance)
4437+
if err != nil {
4438+
t.Fatalf("Listing logical views: %v", err)
4439+
}
4440+
if got, want := len(logicalViews), 0; got != want {
4441+
t.Fatalf("Listing logical views count: %d, want: != %d", got, want)
4442+
}
4443+
}
4444+
43334445
// TestIntegration_DirectPathFallback tests the CFE fallback when the directpath net is blackholed.
43344446
func TestIntegration_DirectPathFallback(t *testing.T) {
43354447
ctx := context.Background()

0 commit comments

Comments
 (0)