go.temporal.io/server@v1.23.0/common/persistence/sql/metadata.go (about)

     1  // The MIT License
     2  //
     3  // Copyright (c) 2020 Temporal Technologies Inc.  All rights reserved.
     4  //
     5  // Copyright (c) 2020 Uber Technologies, Inc.
     6  //
     7  // Permission is hereby granted, free of charge, to any person obtaining a copy
     8  // of this software and associated documentation files (the "Software"), to deal
     9  // in the Software without restriction, including without limitation the rights
    10  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    11  // copies of the Software, and to permit persons to whom the Software is
    12  // furnished to do so, subject to the following conditions:
    13  //
    14  // The above copyright notice and this permission notice shall be included in
    15  // all copies or substantial portions of the Software.
    16  //
    17  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    18  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    19  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    20  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    21  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    22  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    23  // THE SOFTWARE.
    24  
    25  package sql
    26  
    27  import (
    28  	"context"
    29  	"database/sql"
    30  	"fmt"
    31  
    32  	"go.temporal.io/api/serviceerror"
    33  
    34  	"go.temporal.io/server/common/log"
    35  	"go.temporal.io/server/common/persistence"
    36  	"go.temporal.io/server/common/persistence/sql/sqlplugin"
    37  	"go.temporal.io/server/common/primitives"
    38  )
    39  
    40  type sqlMetadataManagerV2 struct {
    41  	SqlStore
    42  	activeClusterName string
    43  }
    44  
    45  // newMetadataPersistenceV2 creates an instance of sqlMetadataManagerV2
    46  func newMetadataPersistenceV2(
    47  	db sqlplugin.DB,
    48  	currentClusterName string,
    49  	logger log.Logger,
    50  ) (persistence.MetadataStore, error) {
    51  	return &sqlMetadataManagerV2{
    52  		SqlStore:          NewSqlStore(db, logger),
    53  		activeClusterName: currentClusterName,
    54  	}, nil
    55  }
    56  
    57  func (m *sqlMetadataManagerV2) CreateNamespace(
    58  	ctx context.Context,
    59  	request *persistence.InternalCreateNamespaceRequest,
    60  ) (*persistence.CreateNamespaceResponse, error) {
    61  	idBytes, err := primitives.ParseUUID(request.ID)
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  
    66  	var resp *persistence.CreateNamespaceResponse
    67  	err = m.txExecute(ctx, "CreateNamespace", func(tx sqlplugin.Tx) error {
    68  		metadata, err := lockMetadata(ctx, tx)
    69  		if err != nil {
    70  			return err
    71  		}
    72  		if _, err := tx.InsertIntoNamespace(ctx, &sqlplugin.NamespaceRow{
    73  			Name:                request.Name,
    74  			ID:                  idBytes,
    75  			Data:                request.Namespace.Data,
    76  			DataEncoding:        request.Namespace.EncodingType.String(),
    77  			IsGlobal:            request.IsGlobal,
    78  			NotificationVersion: metadata.NotificationVersion,
    79  		}); err != nil {
    80  			if m.Db.IsDupEntryError(err) {
    81  				return serviceerror.NewNamespaceAlreadyExists(fmt.Sprintf("name: %v", request.Name))
    82  			}
    83  			return err
    84  		}
    85  		if err := updateMetadata(ctx,
    86  			tx,
    87  			metadata.NotificationVersion,
    88  		); err != nil {
    89  			return err
    90  		}
    91  		resp = &persistence.CreateNamespaceResponse{ID: request.ID}
    92  		return nil
    93  	})
    94  	return resp, err
    95  }
    96  
    97  func (m *sqlMetadataManagerV2) GetNamespace(
    98  	ctx context.Context,
    99  	request *persistence.GetNamespaceRequest,
   100  ) (*persistence.InternalGetNamespaceResponse, error) {
   101  	idBytes, err := primitives.ParseUUID(request.ID)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  	filter := sqlplugin.NamespaceFilter{}
   106  	switch {
   107  	case request.Name != "" && request.ID != "":
   108  		return nil, serviceerror.NewInvalidArgument("GetNamespace operation failed.  Both ID and Name specified in request.")
   109  	case request.Name != "":
   110  		filter.Name = &request.Name
   111  	case len(request.ID) != 0:
   112  		filter.ID = &idBytes
   113  	default:
   114  		return nil, serviceerror.NewInvalidArgument("GetNamespace operation failed.  Both ID and Name are empty.")
   115  	}
   116  
   117  	rows, err := m.Db.SelectFromNamespace(ctx, filter)
   118  	if err != nil {
   119  		switch err {
   120  		case sql.ErrNoRows:
   121  			// We did not return in the above for-loop because there were no rows.
   122  			identity := request.Name
   123  			if len(request.ID) > 0 {
   124  				identity = request.ID
   125  			}
   126  
   127  			return nil, serviceerror.NewNamespaceNotFound(identity)
   128  		default:
   129  			return nil, serviceerror.NewUnavailable(fmt.Sprintf("GetNamespace operation failed. Error %v", err))
   130  		}
   131  	}
   132  
   133  	response, err := m.namespaceRowToGetNamespaceResponse(&rows[0])
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  
   138  	return response, nil
   139  }
   140  
   141  func (m *sqlMetadataManagerV2) namespaceRowToGetNamespaceResponse(row *sqlplugin.NamespaceRow) (*persistence.InternalGetNamespaceResponse, error) {
   142  	return &persistence.InternalGetNamespaceResponse{
   143  		Namespace:           persistence.NewDataBlob(row.Data, row.DataEncoding),
   144  		IsGlobal:            row.IsGlobal,
   145  		NotificationVersion: row.NotificationVersion,
   146  	}, nil
   147  }
   148  
   149  func (m *sqlMetadataManagerV2) UpdateNamespace(
   150  	ctx context.Context,
   151  	request *persistence.InternalUpdateNamespaceRequest,
   152  ) error {
   153  	return m.updateNamespace(ctx, request, "UpdateNamespace")
   154  }
   155  
   156  func (m *sqlMetadataManagerV2) RenameNamespace(
   157  	ctx context.Context,
   158  	request *persistence.InternalRenameNamespaceRequest,
   159  ) error {
   160  	return m.updateNamespace(ctx, request.InternalUpdateNamespaceRequest, "RenameNamespace")
   161  }
   162  
   163  func (m *sqlMetadataManagerV2) updateNamespace(
   164  	ctx context.Context,
   165  	request *persistence.InternalUpdateNamespaceRequest,
   166  	operationName string,
   167  ) error {
   168  	idBytes, err := primitives.ParseUUID(request.Id)
   169  	if err != nil {
   170  		return err
   171  	}
   172  
   173  	return m.txExecute(ctx, operationName, func(tx sqlplugin.Tx) error {
   174  		metadata, err := lockMetadata(ctx, tx)
   175  		if err != nil {
   176  			return err
   177  		}
   178  		if metadata.NotificationVersion != request.NotificationVersion {
   179  			return fmt.Errorf(
   180  				"conditional update error: expect: %v, actual: %v",
   181  				request.NotificationVersion,
   182  				metadata.NotificationVersion,
   183  			)
   184  		}
   185  		result, err := tx.UpdateNamespace(ctx, &sqlplugin.NamespaceRow{
   186  			Name:                request.Name,
   187  			ID:                  idBytes,
   188  			Data:                request.Namespace.Data,
   189  			DataEncoding:        request.Namespace.EncodingType.String(),
   190  			NotificationVersion: request.NotificationVersion,
   191  			IsGlobal:            request.IsGlobal,
   192  		})
   193  		if err != nil {
   194  			return err
   195  		}
   196  		noRowsAffected, err := result.RowsAffected()
   197  		if err != nil {
   198  			return fmt.Errorf("rowsAffected error: %v", err)
   199  		}
   200  		if noRowsAffected != 1 {
   201  			return fmt.Errorf("%v rows updated instead of one", noRowsAffected)
   202  		}
   203  		return updateMetadata(ctx, tx, metadata.NotificationVersion)
   204  	})
   205  }
   206  
   207  func (m *sqlMetadataManagerV2) DeleteNamespace(
   208  	ctx context.Context,
   209  	request *persistence.DeleteNamespaceRequest,
   210  ) error {
   211  	idBytes, err := primitives.ParseUUID(request.ID)
   212  	if err != nil {
   213  		return err
   214  	}
   215  
   216  	return m.txExecute(ctx, "DeleteNamespace", func(tx sqlplugin.Tx) error {
   217  		_, err := tx.DeleteFromNamespace(ctx, sqlplugin.NamespaceFilter{
   218  			ID: &idBytes,
   219  		})
   220  		return err
   221  	})
   222  }
   223  
   224  func (m *sqlMetadataManagerV2) DeleteNamespaceByName(
   225  	ctx context.Context,
   226  	request *persistence.DeleteNamespaceByNameRequest,
   227  ) error {
   228  	return m.txExecute(ctx, "DeleteNamespaceByName", func(tx sqlplugin.Tx) error {
   229  		_, err := tx.DeleteFromNamespace(ctx, sqlplugin.NamespaceFilter{
   230  			Name: &request.Name,
   231  		})
   232  		return err
   233  	})
   234  }
   235  
   236  func (m *sqlMetadataManagerV2) GetMetadata(
   237  	ctx context.Context,
   238  ) (*persistence.GetMetadataResponse, error) {
   239  	row, err := m.Db.SelectFromNamespaceMetadata(ctx)
   240  	if err != nil {
   241  		return nil, serviceerror.NewUnavailable(fmt.Sprintf("GetMetadata operation failed. Error: %v", err))
   242  	}
   243  	return &persistence.GetMetadataResponse{NotificationVersion: row.NotificationVersion}, nil
   244  }
   245  
   246  func (m *sqlMetadataManagerV2) ListNamespaces(
   247  	ctx context.Context,
   248  	request *persistence.InternalListNamespacesRequest,
   249  ) (*persistence.InternalListNamespacesResponse, error) {
   250  	var pageToken *primitives.UUID
   251  	if request.NextPageToken != nil {
   252  		token := primitives.UUID(request.NextPageToken)
   253  		pageToken = &token
   254  	}
   255  	rows, err := m.Db.SelectFromNamespace(ctx, sqlplugin.NamespaceFilter{
   256  		GreaterThanID: pageToken,
   257  		PageSize:      &request.PageSize,
   258  	})
   259  	if err != nil {
   260  		if err == sql.ErrNoRows {
   261  			return &persistence.InternalListNamespacesResponse{}, nil
   262  		}
   263  		return nil, serviceerror.NewUnavailable(fmt.Sprintf("ListNamespaces operation failed. Failed to get namespace rows. Error: %v", err))
   264  	}
   265  
   266  	var namespaces []*persistence.InternalGetNamespaceResponse
   267  	for _, row := range rows {
   268  		resp, err := m.namespaceRowToGetNamespaceResponse(&row)
   269  		if err != nil {
   270  			return nil, err
   271  		}
   272  		namespaces = append(namespaces, resp)
   273  	}
   274  
   275  	resp := &persistence.InternalListNamespacesResponse{Namespaces: namespaces}
   276  	if len(rows) >= request.PageSize {
   277  		resp.NextPageToken = rows[len(rows)-1].ID
   278  	}
   279  
   280  	return resp, nil
   281  }
   282  
   283  func updateMetadata(
   284  	ctx context.Context,
   285  	tx sqlplugin.Tx,
   286  	oldNotificationVersion int64,
   287  ) error {
   288  	result, err := tx.UpdateNamespaceMetadata(ctx, &sqlplugin.NamespaceMetadataRow{
   289  		NotificationVersion: oldNotificationVersion,
   290  	})
   291  	if err != nil {
   292  		return serviceerror.NewUnavailable(fmt.Sprintf("Failed to update namespace metadata. Error: %v", err))
   293  	}
   294  
   295  	rowsAffected, err := result.RowsAffected()
   296  	if err != nil {
   297  		return serviceerror.NewUnavailable(fmt.Sprintf("Could not verify whether namespace metadata update occurred. Error: %v", err))
   298  	} else if rowsAffected != 1 {
   299  		return serviceerror.NewUnavailable(fmt.Sprintf("Failed to update namespace metadata. <>1 rows affected. Error: %v", err))
   300  	}
   301  
   302  	return nil
   303  }
   304  
   305  func lockMetadata(
   306  	ctx context.Context,
   307  	tx sqlplugin.Tx,
   308  ) (*sqlplugin.NamespaceMetadataRow, error) {
   309  	row, err := tx.LockNamespaceMetadata(ctx)
   310  	if err != nil {
   311  		return nil, serviceerror.NewUnavailable(fmt.Sprintf("Failed to lock namespace metadata. Error: %v", err))
   312  	}
   313  	return row, nil
   314  }