github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/doltcore/doltdb/table.go (about)

     1  // Copyright 2019 Dolthub, Inc.
     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 doltdb
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"regexp"
    22  
    23  	"github.com/dolthub/dolt/go/libraries/doltcore/schema"
    24  	"github.com/dolthub/dolt/go/libraries/doltcore/schema/encoding"
    25  	"github.com/dolthub/dolt/go/store/hash"
    26  	"github.com/dolthub/dolt/go/store/types"
    27  )
    28  
    29  const (
    30  	tableStructName = "table"
    31  
    32  	schemaRefKey       = "schema_ref"
    33  	tableRowsKey       = "rows"
    34  	conflictsKey       = "conflicts"
    35  	conflictSchemasKey = "conflict_schemas"
    36  	indexesKey         = "indexes"
    37  	autoIncrementKey   = "auto_increment"
    38  
    39  	// TableNameRegexStr is the regular expression that valid tables must match.
    40  	TableNameRegexStr = `^[a-zA-Z]{1}$|^[a-zA-Z_]+[-_0-9a-zA-Z]*[0-9a-zA-Z]+$`
    41  	// ForeignKeyNameRegexStr is the regular expression that valid foreign keys must match.
    42  	// From the unquoted identifiers: https://dev.mysql.com/doc/refman/8.0/en/identifiers.html
    43  	// We also allow the '-' character from quoted identifiers.
    44  	ForeignKeyNameRegexStr = `^[-$_0-9a-zA-Z]+$`
    45  	// IndexNameRegexStr is the regular expression that valid indexes must match.
    46  	// From the unquoted identifiers: https://dev.mysql.com/doc/refman/8.0/en/identifiers.html
    47  	// We also allow the '-' character from quoted identifiers.
    48  	IndexNameRegexStr = `^[-$_0-9a-zA-Z]+$`
    49  )
    50  
    51  var (
    52  	tableNameRegex      = regexp.MustCompile(TableNameRegexStr)
    53  	foreignKeyNameRegex = regexp.MustCompile(ForeignKeyNameRegexStr)
    54  	indexNameRegex      = regexp.MustCompile(IndexNameRegexStr)
    55  
    56  	ErrNoConflictsResolved  = errors.New("no conflicts resolved")
    57  	ErrNoAutoIncrementValue = fmt.Errorf("auto increment set for non-numeric column type")
    58  )
    59  
    60  // IsValidTableName returns true if the name matches the regular expression TableNameRegexStr.
    61  // Table names must be composed of 1 or more letters and non-initial numerals, as well as the characters _ and -
    62  func IsValidTableName(name string) bool {
    63  	return tableNameRegex.MatchString(name)
    64  }
    65  
    66  // IsValidForeignKeyName returns true if the name matches the regular expression ForeignKeyNameRegexStr.
    67  func IsValidForeignKeyName(name string) bool {
    68  	return foreignKeyNameRegex.MatchString(name)
    69  }
    70  
    71  // IsValidIndexName returns true if the name matches the regular expression IndexNameRegexStr.
    72  func IsValidIndexName(name string) bool {
    73  	return indexNameRegex.MatchString(name)
    74  }
    75  
    76  // Table is a struct which holds row data, as well as a reference to it's schema.
    77  type Table struct {
    78  	vrw         types.ValueReadWriter
    79  	tableStruct types.Struct
    80  }
    81  
    82  // NewTable creates a noms Struct which stores row data, index data, and schema.
    83  func NewTable(ctx context.Context, vrw types.ValueReadWriter, schemaVal types.Value, rowData types.Map, indexData types.Map, autoIncVal types.Value) (*Table, error) {
    84  	schemaRef, err := WriteValAndGetRef(ctx, vrw, schemaVal)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	rowDataRef, err := WriteValAndGetRef(ctx, vrw, rowData)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	indexesRef, err := WriteValAndGetRef(ctx, vrw, indexData)
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  
    99  	sd := types.StructData{
   100  		schemaRefKey: schemaRef,
   101  		tableRowsKey: rowDataRef,
   102  		indexesKey:   indexesRef,
   103  	}
   104  
   105  	if autoIncVal != nil {
   106  		sd[autoIncrementKey] = autoIncVal
   107  	}
   108  
   109  	tableStruct, err := types.NewStruct(vrw.Format(), tableStructName, sd)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  
   114  	return &Table{vrw, tableStruct}, nil
   115  }
   116  
   117  func (t *Table) Format() *types.NomsBinFormat {
   118  	return t.vrw.Format()
   119  }
   120  
   121  // ValueReadWriter returns the ValueReadWriter for this table.
   122  func (t *Table) ValueReadWriter() types.ValueReadWriter {
   123  	return t.vrw
   124  }
   125  
   126  func (t *Table) SetConflicts(ctx context.Context, schemas Conflict, conflictData types.Map) (*Table, error) {
   127  	conflictsRef, err := WriteValAndGetRef(ctx, t.vrw, conflictData)
   128  
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	tpl, err := schemas.ToNomsList(t.vrw)
   134  
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	updatedSt, err := t.tableStruct.Set(conflictSchemasKey, tpl)
   140  
   141  	if err != nil {
   142  		return nil, err
   143  	}
   144  
   145  	updatedSt, err = updatedSt.Set(conflictsKey, conflictsRef)
   146  
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  
   151  	return &Table{t.vrw, updatedSt}, nil
   152  }
   153  
   154  func (t *Table) GetConflicts(ctx context.Context) (Conflict, types.Map, error) {
   155  	schemasVal, ok, err := t.tableStruct.MaybeGet(conflictSchemasKey)
   156  
   157  	if err != nil {
   158  		return Conflict{}, types.EmptyMap, err
   159  	}
   160  
   161  	if !ok {
   162  		return Conflict{}, types.EmptyMap, ErrNoConflicts
   163  	}
   164  
   165  	schemas, err := ConflictFromTuple(schemasVal.(types.Tuple))
   166  
   167  	if err != nil {
   168  		return Conflict{}, types.EmptyMap, err
   169  	}
   170  
   171  	conflictsVal, _, err := t.tableStruct.MaybeGet(conflictsKey)
   172  
   173  	if err != nil {
   174  		return Conflict{}, types.EmptyMap, err
   175  	}
   176  
   177  	confMap := types.EmptyMap
   178  	if conflictsVal != nil {
   179  		confMapRef := conflictsVal.(types.Ref)
   180  		v, err := confMapRef.TargetValue(ctx, t.vrw)
   181  
   182  		if err != nil {
   183  			return Conflict{}, types.EmptyMap, err
   184  		}
   185  
   186  		confMap = v.(types.Map)
   187  	}
   188  
   189  	return schemas, confMap, nil
   190  }
   191  
   192  func (t *Table) HasConflicts() (bool, error) {
   193  	if t == nil {
   194  		return false, nil
   195  	}
   196  
   197  	_, ok, err := t.tableStruct.MaybeGet(conflictSchemasKey)
   198  
   199  	return ok, err
   200  }
   201  
   202  func (t *Table) NumRowsInConflict(ctx context.Context) (uint64, error) {
   203  	if t == nil {
   204  		return 0, nil
   205  	}
   206  
   207  	conflictsVal, ok, err := t.tableStruct.MaybeGet(conflictsKey)
   208  
   209  	if err != nil {
   210  		return 0, err
   211  	}
   212  
   213  	if !ok {
   214  		return 0, nil
   215  	}
   216  
   217  	confMap := types.EmptyMap
   218  	if conflictsVal != nil {
   219  		confMapRef := conflictsVal.(types.Ref)
   220  		v, err := confMapRef.TargetValue(ctx, t.vrw)
   221  
   222  		if err != nil {
   223  			return 0, err
   224  		}
   225  		confMap = v.(types.Map)
   226  	}
   227  
   228  	return confMap.Len(), nil
   229  }
   230  
   231  func (t *Table) ClearConflicts() (*Table, error) {
   232  	tSt, err := t.tableStruct.Delete(conflictSchemasKey)
   233  
   234  	if err != nil {
   235  		return nil, err
   236  	}
   237  
   238  	tSt, err = tSt.Delete(conflictsKey)
   239  
   240  	if err != nil {
   241  		return nil, err
   242  	}
   243  
   244  	return &Table{t.vrw, tSt}, nil
   245  }
   246  
   247  func (t *Table) GetConflictSchemas(ctx context.Context) (base, sch, mergeSch schema.Schema, err error) {
   248  	schemasVal, ok, err := t.tableStruct.MaybeGet(conflictSchemasKey)
   249  
   250  	if err != nil {
   251  		return nil, nil, nil, err
   252  	}
   253  
   254  	if ok {
   255  		schemas, err := ConflictFromTuple(schemasVal.(types.Tuple))
   256  
   257  		if err != nil {
   258  			return nil, nil, nil, err
   259  		}
   260  
   261  		baseRef := schemas.Base.(types.Ref)
   262  		valRef := schemas.Value.(types.Ref)
   263  		mergeRef := schemas.MergeValue.(types.Ref)
   264  
   265  		var baseSch, sch, mergeSch schema.Schema
   266  		if baseSch, err = RefToSchema(ctx, t.vrw, baseRef); err == nil {
   267  			if sch, err = RefToSchema(ctx, t.vrw, valRef); err == nil {
   268  				mergeSch, err = RefToSchema(ctx, t.vrw, mergeRef)
   269  			}
   270  		}
   271  
   272  		return baseSch, sch, mergeSch, err
   273  	}
   274  	return nil, nil, nil, ErrNoConflicts
   275  }
   276  
   277  func RefToSchema(ctx context.Context, vrw types.ValueReadWriter, ref types.Ref) (schema.Schema, error) {
   278  	schemaVal, err := ref.TargetValue(ctx, vrw)
   279  
   280  	if err != nil {
   281  		return nil, err
   282  	}
   283  
   284  	schema, err := encoding.UnmarshalSchemaNomsValue(ctx, vrw.Format(), schemaVal)
   285  
   286  	if err != nil {
   287  		return nil, err
   288  	}
   289  
   290  	return schema, nil
   291  }
   292  
   293  // GetSchema will retrieve the schema being referenced from the table in noms and unmarshal it.
   294  func (t *Table) GetSchema(ctx context.Context) (schema.Schema, error) {
   295  	schemaRefVal, _, err := t.tableStruct.MaybeGet(schemaRefKey)
   296  
   297  	if err != nil {
   298  		return nil, err
   299  	}
   300  
   301  	schemaRef := schemaRefVal.(types.Ref)
   302  	return RefToSchema(ctx, t.vrw, schemaRef)
   303  }
   304  
   305  func (t *Table) GetSchemaRef() (types.Ref, error) {
   306  	v, _, err := t.tableStruct.MaybeGet(schemaRefKey)
   307  
   308  	if err != nil {
   309  		return types.Ref{}, err
   310  	}
   311  
   312  	if v == nil {
   313  		return types.Ref{}, errors.New("missing schema")
   314  	}
   315  
   316  	return v.(types.Ref), nil
   317  }
   318  
   319  // UpdateSchema updates the table with the schema given and returns the updated table. The original table is unchanged.
   320  func (t *Table) UpdateSchema(ctx context.Context, sch schema.Schema) (*Table, error) {
   321  	newSchemaVal, err := encoding.MarshalSchemaAsNomsValue(ctx, t.vrw, sch)
   322  	if err != nil {
   323  		return nil, err
   324  	}
   325  
   326  	schRef, err := WriteValAndGetRef(ctx, t.vrw, newSchemaVal)
   327  	if err != nil {
   328  		return nil, err
   329  	}
   330  
   331  	newTableStruct, err := t.tableStruct.Set(schemaRefKey, schRef)
   332  	if err != nil {
   333  		return nil, err
   334  	}
   335  
   336  	return &Table{t.vrw, newTableStruct}, nil
   337  }
   338  
   339  // HasTheSameSchema tests the schema within 2 tables for equality
   340  func (t *Table) HasTheSameSchema(t2 *Table) (bool, error) {
   341  	schemaVal, _, err := t.tableStruct.MaybeGet(schemaRefKey)
   342  
   343  	if err != nil {
   344  		return false, err
   345  	}
   346  
   347  	schemaRef := schemaVal.(types.Ref)
   348  
   349  	schema2Val, _, err := t2.tableStruct.MaybeGet(schemaRefKey)
   350  
   351  	if err != nil {
   352  		return false, err
   353  	}
   354  
   355  	schema2Ref := schema2Val.(types.Ref)
   356  
   357  	return schemaRef.TargetHash() == schema2Ref.TargetHash(), nil
   358  }
   359  
   360  // HashOf returns the hash of the underlying table struct
   361  func (t *Table) HashOf() (hash.Hash, error) {
   362  	return t.tableStruct.Hash(t.vrw.Format())
   363  }
   364  
   365  // UpdateRows replaces the current row data and returns and updated Table.  Calls to UpdateRows will not be written to the
   366  // database.  The root must be updated with the updated table, and the root must be committed or written.
   367  func (t *Table) UpdateRows(ctx context.Context, updatedRows types.Map) (*Table, error) {
   368  	rowDataRef, err := WriteValAndGetRef(ctx, t.vrw, updatedRows)
   369  
   370  	if err != nil {
   371  		return nil, err
   372  	}
   373  
   374  	updatedSt, err := t.tableStruct.Set(tableRowsKey, rowDataRef)
   375  
   376  	if err != nil {
   377  		return nil, err
   378  	}
   379  
   380  	return &Table{t.vrw, updatedSt}, nil
   381  }
   382  
   383  // GetRowData retrieves the underlying map which is a map from a primary key to a list of field values.
   384  func (t *Table) GetRowData(ctx context.Context) (types.Map, error) {
   385  	val, _, err := t.tableStruct.MaybeGet(tableRowsKey)
   386  
   387  	if err != nil {
   388  		return types.EmptyMap, err
   389  	}
   390  
   391  	rowMapRef := val.(types.Ref)
   392  
   393  	val, err = rowMapRef.TargetValue(ctx, t.vrw)
   394  
   395  	if err != nil {
   396  		return types.EmptyMap, err
   397  	}
   398  
   399  	rowMap := val.(types.Map)
   400  	return rowMap, nil
   401  }
   402  
   403  func (t *Table) ResolveConflicts(ctx context.Context, pkTuples []types.Value) (invalid, notFound []types.Value, tbl *Table, err error) {
   404  	removed := 0
   405  	_, confData, err := t.GetConflicts(ctx)
   406  
   407  	if err != nil {
   408  		return nil, nil, nil, err
   409  	}
   410  
   411  	confEdit := confData.Edit()
   412  	for _, pkTupleVal := range pkTuples {
   413  		if has, err := confData.Has(ctx, pkTupleVal); err != nil {
   414  			return nil, nil, nil, err
   415  		} else if has {
   416  			removed++
   417  			confEdit.Remove(pkTupleVal)
   418  		} else {
   419  			notFound = append(notFound, pkTupleVal)
   420  		}
   421  	}
   422  
   423  	if removed == 0 {
   424  		return invalid, notFound, tbl, ErrNoConflictsResolved
   425  	}
   426  
   427  	conflicts, err := confEdit.Map(ctx)
   428  
   429  	if err != nil {
   430  		return nil, nil, nil, err
   431  	}
   432  
   433  	conflictsRef, err := WriteValAndGetRef(ctx, t.vrw, conflicts)
   434  
   435  	if err != nil {
   436  		return nil, nil, nil, err
   437  	}
   438  
   439  	updatedSt, err := t.tableStruct.Set(conflictsKey, conflictsRef)
   440  
   441  	if err != nil {
   442  		return nil, nil, nil, err
   443  	}
   444  
   445  	return invalid, notFound, &Table{t.vrw, updatedSt}, nil
   446  }
   447  
   448  // GetIndexData returns the internal index map which goes from index name to a ref of the row data map.
   449  func (t *Table) GetIndexData(ctx context.Context) (types.Map, error) {
   450  	indexesVal, ok, err := t.tableStruct.MaybeGet(indexesKey)
   451  	if err != nil {
   452  		return types.EmptyMap, err
   453  	}
   454  	if !ok {
   455  		newEmptyMap, err := types.NewMap(ctx, t.vrw)
   456  		if err != nil {
   457  			return types.EmptyMap, err
   458  		}
   459  		return newEmptyMap, nil
   460  	}
   461  
   462  	indexesMap, err := indexesVal.(types.Ref).TargetValue(ctx, t.vrw)
   463  	if err != nil {
   464  		return types.EmptyMap, err
   465  	}
   466  
   467  	return indexesMap.(types.Map), nil
   468  }
   469  
   470  // SetIndexData replaces the current internal index map, and returns an updated Table.
   471  func (t *Table) SetIndexData(ctx context.Context, indexesMap types.Map) (*Table, error) {
   472  	indexesRef, err := WriteValAndGetRef(ctx, t.vrw, indexesMap)
   473  	if err != nil {
   474  		return nil, err
   475  	}
   476  
   477  	newTableStruct, err := t.tableStruct.Set(indexesKey, indexesRef)
   478  	if err != nil {
   479  		return nil, err
   480  	}
   481  
   482  	return &Table{t.vrw, newTableStruct}, nil
   483  }
   484  
   485  // GetIndexRowData retrieves the underlying map of an index, in which the primary key consists of all indexed columns.
   486  func (t *Table) GetIndexRowData(ctx context.Context, indexName string) (types.Map, error) {
   487  	indexesMap, err := t.GetIndexData(ctx)
   488  	if err != nil {
   489  		return types.EmptyMap, err
   490  	}
   491  
   492  	indexMapRef, ok, err := indexesMap.MaybeGet(ctx, types.String(indexName))
   493  	if err != nil {
   494  		return types.EmptyMap, err
   495  	}
   496  	if !ok {
   497  		return types.EmptyMap, fmt.Errorf("index `%s` is missing its data", indexName)
   498  	}
   499  
   500  	indexMap, err := indexMapRef.(types.Ref).TargetValue(ctx, t.vrw)
   501  	if err != nil {
   502  		return types.EmptyMap, err
   503  	}
   504  
   505  	return indexMap.(types.Map), nil
   506  }
   507  
   508  // SetIndexRowData replaces the current row data for the given index and returns an updated Table.
   509  func (t *Table) SetIndexRowData(ctx context.Context, indexName string, indexRowData types.Map) (*Table, error) {
   510  	indexesMap, err := t.GetIndexData(ctx)
   511  	if err != nil {
   512  		return nil, err
   513  	}
   514  
   515  	indexRowDataRef, err := WriteValAndGetRef(ctx, t.vrw, indexRowData)
   516  	if err != nil {
   517  		return nil, err
   518  	}
   519  	indexesMap, err = indexesMap.Edit().Set(types.String(indexName), indexRowDataRef).Map(ctx)
   520  	if err != nil {
   521  		return nil, err
   522  	}
   523  
   524  	return t.SetIndexData(ctx, indexesMap)
   525  }
   526  
   527  // DeleteIndexRowData removes the underlying map of an index, along with its key entry. This should only be used
   528  // when removing an index altogether. If the intent is to clear an index's data, then use SetIndexRowData with
   529  // an empty map.
   530  func (t *Table) DeleteIndexRowData(ctx context.Context, indexName string) (*Table, error) {
   531  	indexesMap, err := t.GetIndexData(ctx)
   532  	if err != nil {
   533  		return nil, err
   534  	}
   535  
   536  	key := types.String(indexName)
   537  	if has, err := indexesMap.Has(ctx, key); err != nil {
   538  		return nil, err
   539  	} else if has {
   540  		indexesMap, err = indexesMap.Edit().Remove(key).Map(ctx)
   541  		if err != nil {
   542  			return nil, err
   543  		}
   544  	} else {
   545  		return t, nil
   546  	}
   547  
   548  	return t.SetIndexData(ctx, indexesMap)
   549  }
   550  
   551  // RenameIndexRowData changes the name for the index data. Does not verify that the new name is unoccupied. If the old
   552  // name does not exist, then this returns the called table without error.
   553  func (t *Table) RenameIndexRowData(ctx context.Context, oldIndexName, newIndexName string) (*Table, error) {
   554  	indexesMap, err := t.GetIndexData(ctx)
   555  	if err != nil {
   556  		return nil, err
   557  	}
   558  
   559  	oldKey := types.String(oldIndexName)
   560  	newKey := types.String(newIndexName)
   561  	if indexRowData, ok, err := indexesMap.MaybeGet(ctx, oldKey); err != nil {
   562  		return nil, err
   563  	} else if ok {
   564  		indexesMap, err = indexesMap.Edit().Set(newKey, indexRowData).Remove(oldKey).Map(ctx)
   565  		if err != nil {
   566  			return nil, err
   567  		}
   568  	} else {
   569  		return t, nil
   570  	}
   571  
   572  	return t.SetIndexData(ctx, indexesMap)
   573  }
   574  
   575  // VerifyIndexRowData verifies that the index with the given name's data matches what the index expects.
   576  func (t *Table) VerifyIndexRowData(ctx context.Context, indexName string) error {
   577  	sch, err := t.GetSchema(ctx)
   578  	if err != nil {
   579  		return err
   580  	}
   581  
   582  	index := sch.Indexes().GetByName(indexName)
   583  	if index == nil {
   584  		return fmt.Errorf("index `%s` does not exist", indexName)
   585  	}
   586  
   587  	indexesMap, err := t.GetIndexData(ctx)
   588  	if err != nil {
   589  		return err
   590  	}
   591  
   592  	indexMapRef, ok, err := indexesMap.MaybeGet(ctx, types.String(indexName))
   593  	if err != nil {
   594  		return err
   595  	}
   596  	if !ok {
   597  		return fmt.Errorf("index `%s` is missing its data", indexName)
   598  	}
   599  
   600  	indexMapValue, err := indexMapRef.(types.Ref).TargetValue(ctx, t.vrw)
   601  	if err != nil {
   602  		return err
   603  	}
   604  
   605  	iter, err := indexMapValue.(types.Map).Iterator(ctx)
   606  	if err != nil {
   607  		return err
   608  	}
   609  
   610  	return index.VerifyMap(ctx, iter, indexMapValue.(types.Map).Format())
   611  }
   612  
   613  func (t *Table) GetAutoIncrementValue(ctx context.Context) (types.Value, error) {
   614  	val, ok, err := t.tableStruct.MaybeGet(autoIncrementKey)
   615  	if err != nil {
   616  		return nil, err
   617  	}
   618  	if ok {
   619  		return val, nil
   620  	}
   621  
   622  	sch, err := t.GetSchema(ctx)
   623  	if err != nil {
   624  		return nil, err
   625  	}
   626  
   627  	kind := types.UnknownKind
   628  	_ = sch.GetAllCols().Iter(func(tag uint64, col schema.Column) (stop bool, err error) {
   629  		if col.AutoIncrement {
   630  			kind = col.Kind
   631  			stop = true
   632  		}
   633  		return
   634  	})
   635  	switch kind {
   636  	case types.IntKind:
   637  		return types.Int(1), nil
   638  	case types.UintKind:
   639  		return types.Uint(1), nil
   640  	case types.FloatKind:
   641  		return types.Float(1), nil
   642  	default:
   643  		return nil, ErrNoAutoIncrementValue
   644  	}
   645  }
   646  
   647  func (t *Table) SetAutoIncrementValue(val types.Value) (*Table, error) {
   648  	switch val.(type) {
   649  	case types.Int, types.Uint, types.Float:
   650  		st, err := t.tableStruct.Set(autoIncrementKey, val)
   651  		if err != nil {
   652  			return nil, err
   653  		}
   654  		return &Table{t.vrw, st}, nil
   655  
   656  	default:
   657  		return nil, fmt.Errorf("cannot set auto increment to non-numeric value")
   658  	}
   659  }