diff --git a/internal/metadata/repository.go b/internal/metadata/repository.go index 2d31796a8..8a0e460a2 100644 --- a/internal/metadata/repository.go +++ b/internal/metadata/repository.go @@ -630,8 +630,9 @@ func (r *Repository) AddTable(ctx context.Context, tx *sql.Tx, table *Table) err return nil } -func (r *Repository) UpdateTable(ctx context.Context, tx *sql.Tx, table *Table) error { - metadata, err := json.Marshal(table.metadata) +func (r *Repository) UpdateTable(ctx context.Context, tx *sql.Tx, table *Table, metadata map[string]interface{}) error { + marshalledMetadata, err := json.Marshal(metadata) + if err != nil { return err } @@ -640,7 +641,7 @@ func (r *Repository) UpdateTable(ctx context.Context, tx *sql.Tx, table *Table) sql.Named("id", table.ID), sql.Named("projectID", table.ProjectID), sql.Named("datasetID", table.DatasetID), - sql.Named("metadata", string(metadata)), + sql.Named("metadata", string(marshalledMetadata)), ); err != nil { return err } diff --git a/internal/metadata/table.go b/internal/metadata/table.go index 49bce89f8..cbe38f2cb 100644 --- a/internal/metadata/table.go +++ b/internal/metadata/table.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + "maps" "github.com/goccy/go-json" bigqueryv2 "google.golang.org/api/bigquery/v2" @@ -18,7 +19,17 @@ type Table struct { } func (t *Table) Update(ctx context.Context, tx *sql.Tx, metadata map[string]interface{}) error { - return t.repo.UpdateTable(ctx, tx, t) + mergedMetadata := map[string]interface{}{} + maps.Copy(mergedMetadata, t.metadata) + for key, value := range metadata { + mergedMetadata[key] = value + } + + err := t.repo.UpdateTable(ctx, tx, t, mergedMetadata) + if err == nil { + t.metadata = mergedMetadata + } + return err } func (t *Table) Insert(ctx context.Context, tx *sql.Tx) error { diff --git a/server/handler.go b/server/handler.go index 866b6600a..003c5b033 100644 --- a/server/handler.go +++ b/server/handler.go @@ -2689,43 +2689,42 @@ func (h *tablesPatchHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { project := projectFromContext(ctx) dataset := datasetFromContext(ctx) table := tableFromContext(ctx) - var newTable bigqueryv2.Table + var newTable map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&newTable); err != nil { errorResponse(ctx, w, errInvalid(err.Error())) return } + + if _, found := newTable["schema"]; found { + errorResponse(ctx, w, errInvalid("schema updates unsupported")) + return + } + res, err := h.Handle(ctx, &tablesPatchRequest{ - server: server, - project: project, - dataset: dataset, - table: table, - newTable: &newTable, + server: server, + project: project, + dataset: dataset, + table: table, + newTableMetadata: newTable, }) + if err != nil { errorResponse(ctx, w, errInternalError(err.Error())) return } encodeResponse(ctx, w, res) + } type tablesPatchRequest struct { - server *Server - project *metadata.Project - dataset *metadata.Dataset - table *metadata.Table - newTable *bigqueryv2.Table + server *Server + project *metadata.Project + dataset *metadata.Dataset + table *metadata.Table + newTableMetadata map[string]interface{} } func (h *tablesPatchHandler) Handle(ctx context.Context, r *tablesPatchRequest) (*bigqueryv2.Table, error) { - encodedTableData, err := json.Marshal(r.newTable) - if err != nil { - return nil, err - } - var tableMetadata map[string]interface{} - if err := json.Unmarshal(encodedTableData, &tableMetadata); err != nil { - return nil, err - } - conn, err := r.server.connMgr.Connection(ctx, r.project.ID, r.dataset.ID) if err != nil { return nil, err @@ -2735,13 +2734,17 @@ func (h *tablesPatchHandler) Handle(ctx context.Context, r *tablesPatchRequest) return nil, err } defer tx.RollbackIfNotCommitted() - if err := r.table.Update(ctx, tx.Tx(), tableMetadata); err != nil { + if err := r.table.Update(ctx, tx.Tx(), r.newTableMetadata); err != nil { return nil, err } if err := tx.Commit(); err != nil { return nil, err } - return r.newTable, nil + table, err := r.table.Content() + if err != nil { + return nil, err + } + return table, nil } func (h *tablesSetIamPolicyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { diff --git a/server/server_test.go b/server/server_test.go index 8eeaa4c2c..934dc1eaa 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -12,6 +12,7 @@ import ( "net/url" "path/filepath" "strconv" + "strings" "testing" "time" @@ -2581,3 +2582,66 @@ func TestInformationSchema(t *testing.T) { }) } + +func TestPatchTable(t *testing.T) { + ctx := context.Background() + + const ( + projectName = "test" + ) + + bqServer, err := server.New(server.TempStorage) + if err != nil { + t.Fatal(err) + } + if err := bqServer.SetProject(projectName); err != nil { + t.Fatal(err) + } + if err := bqServer.Load(server.YAMLSource(filepath.Join("testdata", "data.yaml"))); err != nil { + t.Fatal(err) + } + + testServer := bqServer.TestServer() + defer func() { + testServer.Close() + bqServer.Stop(ctx) + }() + + client, err := bigquery.NewClient( + ctx, + projectName, + option.WithEndpoint(testServer.URL), + option.WithoutAuthentication(), + ) + if err != nil { + t.Fatal(err) + } + defer client.Close() + description := "new description!" + metadata, err := client.Dataset("dataset1").Table("table_a").Update( + ctx, + bigquery.TableMetadataToUpdate{ + Description: description, + }, + "", + ) + if err != nil { + t.Fatal(err) + } + if metadata.Description != description { + t.Fatalf("Expected updated description; got [%s]", metadata.Description) + } + + metadata, err = client.Dataset("dataset1").Table("table_a").Update( + ctx, + bigquery.TableMetadataToUpdate{ + Schema: bigquery.Schema{ + {Name: "test_col", Type: bigquery.StringFieldType}, + }, + }, + "", + ) + if err == nil || !strings.Contains(err.Error(), "schema updates unsupported") { + t.Fatalf("expected unsupported schema update error; got [%s]", err) + } +}