github.com/matrixorigin/matrixone@v0.7.0/pkg/vm/engine/disttae/table.go (about)

     1  // Copyright 2022 Matrix Origin
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package disttae
    16  
    17  import (
    18  	"context"
    19  	"math/rand"
    20  	"strings"
    21  
    22  	"github.com/matrixorigin/matrixone/pkg/common/moerr"
    23  	plan2 "github.com/matrixorigin/matrixone/pkg/sql/plan"
    24  
    25  	"github.com/matrixorigin/matrixone/pkg/catalog"
    26  	"github.com/matrixorigin/matrixone/pkg/container/batch"
    27  	"github.com/matrixorigin/matrixone/pkg/container/types"
    28  	"github.com/matrixorigin/matrixone/pkg/container/vector"
    29  	"github.com/matrixorigin/matrixone/pkg/pb/plan"
    30  	"github.com/matrixorigin/matrixone/pkg/txn/storage/memorystorage/memtable"
    31  	"github.com/matrixorigin/matrixone/pkg/vm/engine"
    32  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/compute"
    33  )
    34  
    35  var _ engine.Relation = new(table)
    36  
    37  func (tbl *table) Stats(ctx context.Context, expr *plan.Expr) (*plan.Stats, error) {
    38  	switch tbl.tableId {
    39  	case catalog.MO_DATABASE_ID, catalog.MO_TABLES_ID, catalog.MO_COLUMNS_ID:
    40  		return plan2.DefaultStats(), nil
    41  	}
    42  
    43  	if tbl.meta == nil || !tbl.updated {
    44  		err := GetTableMeta(ctx, tbl, expr)
    45  		if err != nil {
    46  			return plan2.DefaultStats(), err
    47  		}
    48  	}
    49  	if tbl.meta != nil {
    50  		return CalcStats(ctx, &tbl.meta.blocks, expr, tbl.getTableDef(), tbl.db.txn.proc, tbl.getCbName())
    51  	} else {
    52  		// no meta means not flushed yet, very small table
    53  		return plan2.DefaultStats(), nil
    54  	}
    55  }
    56  
    57  func (tbl *table) Rows(ctx context.Context) (int64, error) {
    58  	var rows int64
    59  	writes := make([]Entry, 0, len(tbl.db.txn.writes))
    60  	tbl.db.txn.Lock()
    61  	for i := range tbl.db.txn.writes {
    62  		for _, entry := range tbl.db.txn.writes[i] {
    63  			if entry.databaseId == tbl.db.databaseId &&
    64  				entry.tableId == tbl.tableId {
    65  				writes = append(writes, entry)
    66  			}
    67  		}
    68  	}
    69  	tbl.db.txn.Unlock()
    70  
    71  	deletes := make(map[types.Rowid]uint8)
    72  	for _, entry := range writes {
    73  		if entry.typ == INSERT {
    74  			rows = rows + int64(entry.bat.Length())
    75  		} else {
    76  			if entry.bat.GetVector(0).GetType().Oid == types.T_Rowid {
    77  				vs := vector.MustTCols[types.Rowid](entry.bat.GetVector(0))
    78  				for _, v := range vs {
    79  					deletes[v] = 0
    80  				}
    81  			}
    82  		}
    83  	}
    84  
    85  	t := memtable.Time{
    86  		Timestamp: tbl.db.txn.meta.SnapshotTS,
    87  	}
    88  	tx := memtable.NewTransaction(
    89  		newMemTableTransactionID(),
    90  		t,
    91  		memtable.SnapshotIsolation,
    92  	)
    93  	for _, partition := range tbl.parts {
    94  		pRows, err := partition.Rows(tx, deletes, tbl.skipBlocks)
    95  		if err != nil {
    96  			return 0, err
    97  		}
    98  		rows = rows + pRows
    99  	}
   100  
   101  	if tbl.meta == nil {
   102  		return rows, nil
   103  	}
   104  	if tbl.meta != nil {
   105  		for _, blks := range tbl.meta.blocks {
   106  			for _, blk := range blks {
   107  				rows += blockRows(blk)
   108  			}
   109  		}
   110  	}
   111  	return rows, nil
   112  }
   113  
   114  func (tbl *table) MaxAndMinValues(ctx context.Context) ([][2]any, []uint8, error) {
   115  	cols := tbl.getTableDef().GetCols()
   116  	dataLength := len(cols) - 1
   117  	//dateType of each column for table
   118  	tableTypes := make([]uint8, dataLength)
   119  	dataTypes := make([]types.Type, dataLength)
   120  
   121  	columns := make([]int, dataLength)
   122  	for i := 0; i < dataLength; i++ {
   123  		columns[i] = i
   124  	}
   125  	//minimum --- maximum
   126  	tableVal := make([][2]any, dataLength)
   127  
   128  	if tbl.meta == nil {
   129  		return nil, nil, moerr.NewInvalidInputNoCtx("table meta is nil")
   130  	}
   131  
   132  	var init bool
   133  	for _, blks := range tbl.meta.blocks {
   134  		for _, blk := range blks {
   135  			blkVal, blkTypes, err := getZonemapDataFromMeta(ctx, columns, blk, tbl.getTableDef())
   136  			if err != nil {
   137  				return nil, nil, err
   138  			}
   139  
   140  			if !init {
   141  				//init the tableVal
   142  				init = true
   143  
   144  				for i := range blkVal {
   145  					tableVal[i][0] = blkVal[i][0]
   146  					tableVal[i][1] = blkVal[i][1]
   147  					dataTypes[i] = types.T(blkTypes[i]).ToType()
   148  				}
   149  
   150  				tableTypes = blkTypes
   151  			} else {
   152  				for i := range blkVal {
   153  					if compute.CompareGeneric(blkVal[i][0], tableVal[i][0], dataTypes[i]) < 0 {
   154  						tableVal[i][0] = blkVal[i][0]
   155  					}
   156  
   157  					if compute.CompareGeneric(blkVal[i][1], tableVal[i][1], dataTypes[i]) > 0 {
   158  						tableVal[i][1] = blkVal[i][1]
   159  					}
   160  				}
   161  			}
   162  		}
   163  	}
   164  	return tableVal, tableTypes, nil
   165  }
   166  
   167  func (tbl *table) Size(ctx context.Context, name string) (int64, error) {
   168  	// TODO
   169  	return 0, nil
   170  }
   171  
   172  // return all unmodified blocks
   173  func (tbl *table) Ranges(ctx context.Context, expr *plan.Expr) ([][]byte, error) {
   174  	/*
   175  		// consider halloween problem
   176  		if int64(tbl.db.txn.statementId)-1 > 0 {
   177  			writes = tbl.db.txn.writes[:tbl.db.txn.statementId-1]
   178  		}
   179  	*/
   180  	writes := make([]Entry, 0, len(tbl.db.txn.writes))
   181  	for i := range tbl.db.txn.writes {
   182  		for _, entry := range tbl.db.txn.writes[i] {
   183  			if entry.databaseId == tbl.db.databaseId &&
   184  				entry.tableId == tbl.tableId {
   185  				writes = append(writes, entry)
   186  			}
   187  		}
   188  	}
   189  
   190  	err := GetTableMeta(ctx, tbl, expr)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  
   195  	ranges := make([][]byte, 0, 1)
   196  	ranges = append(ranges, []byte{})
   197  	tbl.skipBlocks = make(map[uint64]uint8)
   198  	if tbl.meta == nil {
   199  		return ranges, nil
   200  	}
   201  	tbl.meta.modifedBlocks = make([][]ModifyBlockMeta, len(tbl.meta.blocks))
   202  
   203  	exprMono := plan2.CheckExprIsMonotonic(tbl.db.txn.proc.Ctx, expr)
   204  	columnMap, columns, maxCol := plan2.GetColumnsByExpr(expr, tbl.getTableDef())
   205  	for _, i := range tbl.dnList {
   206  		blks, deletes := tbl.parts[i].BlockList(ctx, tbl.db.txn.meta.SnapshotTS,
   207  			tbl.meta.blocks[i], writes)
   208  		for _, blk := range blks {
   209  			tbl.skipBlocks[blk.Info.BlockID] = 0
   210  			if !exprMono || needRead(ctx, expr, blk, tbl.getTableDef(), columnMap, columns, maxCol, tbl.db.txn.proc) {
   211  				ranges = append(ranges, blockMarshal(blk))
   212  			}
   213  		}
   214  		tbl.meta.modifedBlocks[i] = genModifedBlocks(ctx, deletes,
   215  			tbl.meta.blocks[i], blks, expr, tbl.getTableDef(), tbl.db.txn.proc)
   216  	}
   217  	return ranges, nil
   218  }
   219  
   220  // getTableDef only return all cols and their index.
   221  func (tbl *table) getTableDef() *plan.TableDef {
   222  	if tbl.tableDef == nil {
   223  		var cols []*plan.ColDef
   224  		i := int32(0)
   225  		name2index := make(map[string]int32)
   226  		for _, def := range tbl.defs {
   227  			if attr, ok := def.(*engine.AttributeDef); ok {
   228  				name2index[attr.Attr.Name] = i
   229  				cols = append(cols, &plan.ColDef{
   230  					Name:  attr.Attr.Name,
   231  					ColId: attr.Attr.ID,
   232  					Typ: &plan.Type{
   233  						Id:        int32(attr.Attr.Type.Oid),
   234  						Width:     attr.Attr.Type.Width,
   235  						Size:      attr.Attr.Type.Size,
   236  						Precision: attr.Attr.Type.Precision,
   237  						Scale:     attr.Attr.Type.Scale,
   238  						AutoIncr:  attr.Attr.AutoIncrement,
   239  					},
   240  					Primary:   attr.Attr.Primary,
   241  					Default:   attr.Attr.Default,
   242  					OnUpdate:  attr.Attr.OnUpdate,
   243  					Comment:   attr.Attr.Comment,
   244  					ClusterBy: attr.Attr.ClusterBy,
   245  				})
   246  				i++
   247  			}
   248  		}
   249  		tbl.tableDef = &plan.TableDef{
   250  			Name:          tbl.tableName,
   251  			Cols:          cols,
   252  			Name2ColIndex: name2index,
   253  		}
   254  	}
   255  	return tbl.tableDef
   256  }
   257  
   258  func (tbl *table) TableDefs(ctx context.Context) ([]engine.TableDef, error) {
   259  	//return tbl.defs, nil
   260  	// I don't understand why the logic now is not to get all the tableDef. Don't understand.
   261  	// copy from tae's logic
   262  	defs := make([]engine.TableDef, 0, len(tbl.defs))
   263  	if tbl.comment != "" {
   264  		commentDef := new(engine.CommentDef)
   265  		commentDef.Comment = tbl.comment
   266  		defs = append(defs, commentDef)
   267  	}
   268  	if tbl.partition != "" {
   269  		partitionDef := new(engine.PartitionDef)
   270  		partitionDef.Partition = tbl.partition
   271  		defs = append(defs, partitionDef)
   272  	}
   273  
   274  	if tbl.viewdef != "" {
   275  		viewDef := new(engine.ViewDef)
   276  		viewDef.View = tbl.viewdef
   277  		defs = append(defs, viewDef)
   278  	}
   279  	if len(tbl.constraint) > 0 {
   280  		c := &engine.ConstraintDef{}
   281  		err := c.UnmarshalBinary(tbl.constraint)
   282  		if err != nil {
   283  			return nil, err
   284  		}
   285  		defs = append(defs, c)
   286  	}
   287  	for i, def := range tbl.defs {
   288  		if attr, ok := def.(*engine.AttributeDef); ok {
   289  			if attr.Attr.Name != catalog.Row_ID {
   290  				defs = append(defs, tbl.defs[i])
   291  			}
   292  		}
   293  	}
   294  	pro := new(engine.PropertiesDef)
   295  	pro.Properties = append(pro.Properties, engine.Property{
   296  		Key:   catalog.SystemRelAttr_Kind,
   297  		Value: string(tbl.relKind),
   298  	})
   299  	if tbl.createSql != "" {
   300  		pro.Properties = append(pro.Properties, engine.Property{
   301  			Key:   catalog.SystemRelAttr_CreateSQL,
   302  			Value: tbl.createSql,
   303  		})
   304  	}
   305  	defs = append(defs, pro)
   306  	return defs, nil
   307  
   308  }
   309  
   310  func (tbl *table) UpdateConstraint(ctx context.Context, c *engine.ConstraintDef) error {
   311  	ct, err := c.MarshalBinary()
   312  	if err != nil {
   313  		return err
   314  	}
   315  	bat, err := genTableConstraintTuple(tbl.tableId, tbl.db.databaseId, tbl.tableName, tbl.db.databaseName, ct, tbl.db.txn.proc.Mp())
   316  	if err != nil {
   317  		return err
   318  	}
   319  	if err = tbl.db.txn.WriteBatch(UPDATE, catalog.MO_CATALOG_ID, catalog.MO_TABLES_ID,
   320  		catalog.MO_CATALOG, catalog.MO_TABLES, bat, tbl.db.txn.dnStores[0], -1); err != nil {
   321  		return err
   322  	}
   323  	tbl.constraint = ct
   324  	return nil
   325  }
   326  
   327  func (tbl *table) TableColumns(ctx context.Context) ([]*engine.Attribute, error) {
   328  	var attrs []*engine.Attribute
   329  	for _, def := range tbl.defs {
   330  		if attr, ok := def.(*engine.AttributeDef); ok {
   331  			attrs = append(attrs, &attr.Attr)
   332  		}
   333  	}
   334  	return attrs, nil
   335  }
   336  
   337  func (tbl *table) getCbName() string {
   338  	if tbl.clusterByIdx == -1 {
   339  		return ""
   340  	} else {
   341  		return tbl.tableDef.Cols[tbl.clusterByIdx].Name
   342  	}
   343  }
   344  
   345  func (tbl *table) GetPrimaryKeys(ctx context.Context) ([]*engine.Attribute, error) {
   346  	attrs := make([]*engine.Attribute, 0, 1)
   347  	for _, def := range tbl.defs {
   348  		if attr, ok := def.(*engine.AttributeDef); ok {
   349  			if attr.Attr.Primary {
   350  				attrs = append(attrs, &attr.Attr)
   351  			}
   352  		}
   353  	}
   354  	return attrs, nil
   355  }
   356  
   357  func (tbl *table) GetHideKeys(ctx context.Context) ([]*engine.Attribute, error) {
   358  	attrs := make([]*engine.Attribute, 0, 1)
   359  	attrs = append(attrs, &engine.Attribute{
   360  		IsHidden: true,
   361  		IsRowId:  true,
   362  		Name:     catalog.Row_ID,
   363  		Type:     types.New(types.T_Rowid, 0, 0, 0),
   364  		Primary:  true,
   365  	})
   366  	return attrs, nil
   367  }
   368  
   369  func (tbl *table) Write(ctx context.Context, bat *batch.Batch) error {
   370  	if bat == nil {
   371  		return nil
   372  	}
   373  
   374  	// Write S3 Block
   375  	if bat.Attrs[0] == catalog.BlockMeta_MetaLoc {
   376  		fileName := strings.Split(bat.Vecs[0].GetString(0), ":")[0]
   377  		ibat := batch.New(true, bat.Attrs)
   378  		for j := range bat.Vecs {
   379  			ibat.SetVector(int32(j), vector.New(bat.GetVector(int32(j)).GetType()))
   380  		}
   381  		if _, err := ibat.Append(ctx, tbl.db.txn.proc.Mp(), bat); err != nil {
   382  			return err
   383  		}
   384  		i := rand.Int() % len(tbl.db.txn.dnStores)
   385  		return tbl.db.txn.WriteFile(INSERT, tbl.db.databaseId, tbl.tableId, tbl.db.databaseName, tbl.tableName, fileName, ibat, tbl.db.txn.dnStores[i])
   386  	}
   387  	if tbl.insertExpr == nil {
   388  		ibat := batch.New(true, bat.Attrs)
   389  		for j := range bat.Vecs {
   390  			ibat.SetVector(int32(j), vector.New(bat.GetVector(int32(j)).GetType()))
   391  		}
   392  		if _, err := ibat.Append(ctx, tbl.db.txn.proc.Mp(), bat); err != nil {
   393  			return err
   394  		}
   395  		i := rand.Int() % len(tbl.db.txn.dnStores)
   396  		return tbl.db.txn.WriteBatch(INSERT, tbl.db.databaseId, tbl.tableId,
   397  			tbl.db.databaseName, tbl.tableName, ibat, tbl.db.txn.dnStores[i], tbl.primaryIdx)
   398  	}
   399  	bats, err := partitionBatch(bat, tbl.insertExpr, tbl.db.txn.proc, len(tbl.parts))
   400  	if err != nil {
   401  		return err
   402  	}
   403  	for i := range bats {
   404  		if bats[i].Length() == 0 {
   405  			continue
   406  		}
   407  		if err := tbl.db.txn.WriteBatch(INSERT, tbl.db.databaseId, tbl.tableId,
   408  			tbl.db.databaseName, tbl.tableName, bats[i], tbl.db.txn.dnStores[i], tbl.primaryIdx); err != nil {
   409  			return err
   410  		}
   411  	}
   412  	return nil
   413  }
   414  
   415  func (tbl *table) Update(ctx context.Context, bat *batch.Batch) error {
   416  	return nil
   417  }
   418  
   419  func (tbl *table) Delete(ctx context.Context, bat *batch.Batch, name string) error {
   420  	bat.SetAttributes([]string{catalog.Row_ID})
   421  	bat = tbl.db.txn.deleteBatch(bat, tbl.db.databaseId, tbl.tableId)
   422  	if bat.Length() == 0 {
   423  		return nil
   424  	}
   425  	bats, err := partitionDeleteBatch(tbl, bat)
   426  	if err != nil {
   427  		return err
   428  	}
   429  	for i := range bats {
   430  		if bats[i].Length() == 0 {
   431  			continue
   432  		}
   433  		if err := tbl.db.txn.WriteBatch(DELETE, tbl.db.databaseId, tbl.tableId,
   434  			tbl.db.databaseName, tbl.tableName, bats[i], tbl.db.txn.dnStores[i], tbl.primaryIdx); err != nil {
   435  			return err
   436  		}
   437  	}
   438  	return nil
   439  }
   440  
   441  func (tbl *table) AddTableDef(ctx context.Context, def engine.TableDef) error {
   442  	return nil
   443  }
   444  
   445  func (tbl *table) DelTableDef(ctx context.Context, def engine.TableDef) error {
   446  	return nil
   447  }
   448  
   449  func (tbl *table) GetTableID(ctx context.Context) uint64 {
   450  	return tbl.tableId
   451  }
   452  
   453  func (tbl *table) NewReader(ctx context.Context, num int, expr *plan.Expr, ranges [][]byte) ([]engine.Reader, error) {
   454  	if len(ranges) == 0 {
   455  		return tbl.newMergeReader(ctx, num, expr)
   456  	}
   457  	if len(ranges) == 1 && len(ranges[0]) == 0 {
   458  		return tbl.newMergeReader(ctx, num, expr)
   459  	}
   460  	if len(ranges) > 1 && len(ranges[0]) == 0 {
   461  		rds := make([]engine.Reader, num)
   462  		mrds := make([]mergeReader, num)
   463  		rds0, err := tbl.newMergeReader(ctx, num, expr)
   464  		if err != nil {
   465  			return nil, err
   466  		}
   467  		for i := range rds0 {
   468  			mrds[i].rds = append(mrds[i].rds, rds0[i])
   469  		}
   470  		rds0, err = tbl.newBlockReader(ctx, num, expr, ranges[1:])
   471  		if err != nil {
   472  			return nil, err
   473  		}
   474  		for i := range rds0 {
   475  			mrds[i].rds = append(mrds[i].rds, rds0[i])
   476  		}
   477  		for i := range rds {
   478  			rds[i] = &mrds[i]
   479  		}
   480  		return rds, nil
   481  	}
   482  	return tbl.newBlockReader(ctx, num, expr, ranges)
   483  }
   484  
   485  func (tbl *table) newMergeReader(ctx context.Context, num int,
   486  	expr *plan.Expr) ([]engine.Reader, error) {
   487  	var index memtable.Tuple
   488  	/*
   489  		// consider halloween problem
   490  		if int64(tbl.db.txn.statementId)-1 > 0 {
   491  			writes = tbl.db.txn.writes[:tbl.db.txn.statementId-1]
   492  		}
   493  	*/
   494  	if tbl.primaryIdx >= 0 && expr != nil {
   495  		pkColumn := tbl.tableDef.Cols[tbl.primaryIdx]
   496  		ok, v := getPkValueByExpr(expr, pkColumn.Name, types.T(pkColumn.Typ.Id))
   497  		if ok {
   498  			index = memtable.Tuple{
   499  				index_PrimaryKey,
   500  				memtable.ToOrdered(v),
   501  			}
   502  		}
   503  	}
   504  	writes := make([]Entry, 0, len(tbl.db.txn.writes))
   505  	tbl.db.txn.Lock()
   506  	for i := range tbl.db.txn.writes {
   507  		for _, entry := range tbl.db.txn.writes[i] {
   508  			if entry.databaseId == tbl.db.databaseId &&
   509  				entry.tableId == tbl.tableId {
   510  				writes = append(writes, entry)
   511  			}
   512  		}
   513  	}
   514  	tbl.db.txn.Unlock()
   515  	rds := make([]engine.Reader, num)
   516  	mrds := make([]mergeReader, num)
   517  	for _, i := range tbl.dnList {
   518  		var blks []ModifyBlockMeta
   519  
   520  		if tbl.meta != nil {
   521  			blks = tbl.meta.modifedBlocks[i]
   522  		}
   523  		tbl.parts[i].txn = tbl.db.txn
   524  		rds0, err := tbl.parts[i].NewReader(ctx, num, index, tbl.defs, tbl.tableDef,
   525  			tbl.skipBlocks, blks, tbl.db.txn.meta.SnapshotTS, tbl.db.fs, writes)
   526  		if err != nil {
   527  			return nil, err
   528  		}
   529  		for i := range rds0 {
   530  			mrds[i].rds = append(mrds[i].rds, rds0[i])
   531  		}
   532  	}
   533  	for i := range rds {
   534  		rds[i] = &mrds[i]
   535  	}
   536  	return rds, nil
   537  }
   538  
   539  func (tbl *table) newBlockReader(ctx context.Context, num int, expr *plan.Expr, ranges [][]byte) ([]engine.Reader, error) {
   540  	rds := make([]engine.Reader, num)
   541  	blks := make([]BlockMeta, len(ranges))
   542  	for i := range ranges {
   543  		blks[i] = blockUnmarshal(ranges[i])
   544  	}
   545  	ts := tbl.db.txn.meta.SnapshotTS
   546  	tableDef := tbl.getTableDef()
   547  
   548  	if len(ranges) < num {
   549  		for i := range ranges {
   550  			rds[i] = &blockReader{
   551  				fs:         tbl.db.fs,
   552  				tableDef:   tableDef,
   553  				primaryIdx: tbl.primaryIdx,
   554  				expr:       expr,
   555  				ts:         ts,
   556  				ctx:        ctx,
   557  				blks:       []BlockMeta{blks[i]},
   558  			}
   559  		}
   560  		for j := len(ranges); j < num; j++ {
   561  			rds[j] = &emptyReader{}
   562  		}
   563  		return rds, nil
   564  	}
   565  	step := (len(ranges)) / num
   566  	if step < 1 {
   567  		step = 1
   568  	}
   569  	for i := 0; i < num; i++ {
   570  		if i == num-1 {
   571  			rds[i] = &blockReader{
   572  				fs:         tbl.db.fs,
   573  				tableDef:   tableDef,
   574  				primaryIdx: tbl.primaryIdx,
   575  				expr:       expr,
   576  				ts:         ts,
   577  				ctx:        ctx,
   578  				blks:       blks[i*step:],
   579  			}
   580  		} else {
   581  			rds[i] = &blockReader{
   582  				fs:         tbl.db.fs,
   583  				tableDef:   tableDef,
   584  				primaryIdx: tbl.primaryIdx,
   585  				expr:       expr,
   586  				ts:         ts,
   587  				ctx:        ctx,
   588  				blks:       blks[i*step : (i+1)*step],
   589  			}
   590  		}
   591  	}
   592  	return rds, nil
   593  }