github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/sqle/sqlupdate_test.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 sqle
    16  
    17  import (
    18  	"context"
    19  	"testing"
    20  
    21  	"github.com/dolthub/go-mysql-server/sql"
    22  	gmstypes "github.com/dolthub/go-mysql-server/sql/types"
    23  	"github.com/google/uuid"
    24  	"github.com/stretchr/testify/assert"
    25  	"github.com/stretchr/testify/require"
    26  
    27  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    28  	"github.com/dolthub/dolt/go/libraries/doltcore/schema"
    29  	"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dtables"
    30  	"github.com/dolthub/dolt/go/libraries/doltcore/sqle/json"
    31  )
    32  
    33  // Set to the name of a single test to run just that test, useful for debugging
    34  const singleUpdateQueryTest = "" //"Natural join with join clause"
    35  
    36  // Set to false to run tests known to be broken
    37  const skipBrokenUpdate = true
    38  
    39  // Structure for a test of an update query
    40  type UpdateTest struct {
    41  	// The name of this test. Names should be unique and descriptive.
    42  	Name string
    43  	// The update query to run
    44  	UpdateQuery string
    45  	// The select query to run to verify the results
    46  	SelectQuery string
    47  	// The schema of the result of the query, nil if an error is expected
    48  	ExpectedSchema schema.Schema
    49  	// The rows this query should return, nil if an error is expected
    50  	ExpectedRows []sql.Row
    51  	// An expected error string
    52  	ExpectedErr string
    53  	// Setup logic to run before executing this test, after initial tables have been created and populated
    54  	AdditionalSetup SetupFn
    55  }
    56  
    57  // BasicUpdateTests cover basic update statement features and error handling
    58  var BasicUpdateTests = []UpdateTest{
    59  	{
    60  		Name:           "update one row, one col, primary key where clause",
    61  		UpdateQuery:    `update people set first_name = "Domer" where id = 0`,
    62  		SelectQuery:    `select * from people where id = 0`,
    63  		ExpectedRows:   ToSqlRows(PeopleTestSchema, MutateRow(PeopleTestSchema, Homer, FirstNameTag, "Domer")),
    64  		ExpectedSchema: CompressSchema(PeopleTestSchema),
    65  	},
    66  	{
    67  		Name:           "update one row, one col, non-primary key where clause",
    68  		UpdateQuery:    `update people set first_name = "Domer" where first_name = "Homer"`,
    69  		SelectQuery:    `select * from people where first_name = "Domer"`,
    70  		ExpectedRows:   ToSqlRows(PeopleTestSchema, MutateRow(PeopleTestSchema, Homer, FirstNameTag, "Domer")),
    71  		ExpectedSchema: CompressSchema(PeopleTestSchema),
    72  	},
    73  	{
    74  		Name:           "update one row, two cols, primary key where clause",
    75  		UpdateQuery:    `update people set first_name = "Ned", last_name = "Flanders" where id = 0`,
    76  		SelectQuery:    `select * from people where id = 0`,
    77  		ExpectedRows:   ToSqlRows(PeopleTestSchema, MutateRow(PeopleTestSchema, Homer, FirstNameTag, "Ned", LastNameTag, "Flanders")),
    78  		ExpectedSchema: CompressSchema(PeopleTestSchema),
    79  	},
    80  	{
    81  		Name: "update one row, all cols, non-primary key where clause",
    82  		UpdateQuery: `update people set first_name = "Ned", last_name = "Flanders", is_married = false, rating = 10,
    83  				age = 45, num_episodes = 150, uuid = '00000000-0000-0000-0000-000000000050'
    84  				where age = 38`,
    85  		SelectQuery: `select * from people where uuid = '00000000-0000-0000-0000-000000000050'`,
    86  		ExpectedRows: ToSqlRows(PeopleTestSchema,
    87  			MutateRow(PeopleTestSchema, Marge, FirstNameTag, "Ned", LastNameTag, "Flanders", IsMarriedTag, false,
    88  				RatingTag, 10.0, AgeTag, 45, NumEpisodesTag, uint64(150),
    89  				UuidTag, uuid.MustParse("00000000-0000-0000-0000-000000000050"))),
    90  		ExpectedSchema: CompressSchema(PeopleTestSchema),
    91  	},
    92  	{
    93  		Name: "update one row, set columns to existing values",
    94  		UpdateQuery: `update people set first_name = "Homer", last_name = "Simpson", is_married = true, rating = 8.5, age = 40,
    95  				num_episodes = null, uuid = null
    96  				where id = 0`,
    97  		SelectQuery:    `select * from people where id = 0`,
    98  		ExpectedRows:   ToSqlRows(PeopleTestSchema, Homer),
    99  		ExpectedSchema: CompressSchema(PeopleTestSchema),
   100  	},
   101  	{
   102  		Name: "update one row, null out existing values",
   103  		UpdateQuery: `update people set first_name = "Homer", last_name = "Simpson", is_married = null, rating = null, age = null,
   104  				num_episodes = null, uuid = null
   105  				where first_name = "Homer"`,
   106  		SelectQuery:    `select * from people where first_name = "Homer"`,
   107  		ExpectedRows:   ToSqlRows(PeopleTestSchema, MutateRow(PeopleTestSchema, Homer, IsMarriedTag, nil, RatingTag, nil, AgeTag, nil)),
   108  		ExpectedSchema: CompressSchema(PeopleTestSchema),
   109  	},
   110  	{
   111  		Name: "update multiple rows, set two columns",
   112  		UpdateQuery: `update people set first_name = "Changed", rating = 0.0
   113  				where last_name = "Simpson"`,
   114  		SelectQuery: `select * from people where last_name = "Simpson"`,
   115  		ExpectedRows: ToSqlRows(PeopleTestSchema,
   116  			MutateRow(PeopleTestSchema, Homer, FirstNameTag, "Changed", RatingTag, 0.0),
   117  			MutateRow(PeopleTestSchema, Marge, FirstNameTag, "Changed", RatingTag, 0.0),
   118  			MutateRow(PeopleTestSchema, Bart, FirstNameTag, "Changed", RatingTag, 0.0),
   119  			MutateRow(PeopleTestSchema, Lisa, FirstNameTag, "Changed", RatingTag, 0.0),
   120  		),
   121  		ExpectedSchema: CompressSchema(PeopleTestSchema),
   122  	},
   123  	{
   124  		Name:           "update no matching rows",
   125  		UpdateQuery:    `update people set first_name = "Changed", rating = 0.0 where last_name = "Flanders"`,
   126  		SelectQuery:    `select * from people`,
   127  		ExpectedRows:   ToSqlRows(PeopleTestSchema, Homer, Marge, Bart, Lisa, Moe, Barney),
   128  		ExpectedSchema: CompressSchema(PeopleTestSchema),
   129  	},
   130  	{
   131  		Name:        "update without where clause",
   132  		UpdateQuery: `update people set first_name = "Changed", rating = 0.0`,
   133  		SelectQuery: `select * from people`,
   134  		ExpectedRows: ToSqlRows(PeopleTestSchema,
   135  			MutateRow(PeopleTestSchema, Homer, FirstNameTag, "Changed", RatingTag, 0.0),
   136  			MutateRow(PeopleTestSchema, Marge, FirstNameTag, "Changed", RatingTag, 0.0),
   137  			MutateRow(PeopleTestSchema, Bart, FirstNameTag, "Changed", RatingTag, 0.0),
   138  			MutateRow(PeopleTestSchema, Lisa, FirstNameTag, "Changed", RatingTag, 0.0),
   139  			MutateRow(PeopleTestSchema, Moe, FirstNameTag, "Changed", RatingTag, 0.0),
   140  			MutateRow(PeopleTestSchema, Barney, FirstNameTag, "Changed", RatingTag, 0.0),
   141  		),
   142  		ExpectedSchema: CompressSchema(PeopleTestSchema),
   143  	},
   144  	{
   145  		Name:        "update set first_name = last_name",
   146  		UpdateQuery: `update people set first_name = last_name`,
   147  		SelectQuery: `select * from people`,
   148  		ExpectedRows: ToSqlRows(PeopleTestSchema,
   149  			MutateRow(PeopleTestSchema, Homer, FirstNameTag, "Simpson"),
   150  			MutateRow(PeopleTestSchema, Marge, FirstNameTag, "Simpson"),
   151  			MutateRow(PeopleTestSchema, Bart, FirstNameTag, "Simpson"),
   152  			MutateRow(PeopleTestSchema, Lisa, FirstNameTag, "Simpson"),
   153  			MutateRow(PeopleTestSchema, Moe, FirstNameTag, "Szyslak"),
   154  			MutateRow(PeopleTestSchema, Barney, FirstNameTag, "Gumble"),
   155  		),
   156  		ExpectedSchema: CompressSchema(PeopleTestSchema),
   157  	},
   158  	{
   159  		Name:        "update increment age",
   160  		UpdateQuery: `update people set age = age + 1`,
   161  		SelectQuery: `select * from people`,
   162  		ExpectedRows: ToSqlRows(PeopleTestSchema,
   163  			MutateRow(PeopleTestSchema, Homer, AgeTag, 41),
   164  			MutateRow(PeopleTestSchema, Marge, AgeTag, 39),
   165  			MutateRow(PeopleTestSchema, Bart, AgeTag, 11),
   166  			MutateRow(PeopleTestSchema, Lisa, AgeTag, 9),
   167  			MutateRow(PeopleTestSchema, Moe, AgeTag, 49),
   168  			MutateRow(PeopleTestSchema, Barney, AgeTag, 41),
   169  		),
   170  		ExpectedSchema: CompressSchema(PeopleTestSchema),
   171  	},
   172  	{
   173  		Name:        "update reverse rating",
   174  		UpdateQuery: `update people set rating = -rating`,
   175  		SelectQuery: `select * from people`,
   176  		ExpectedRows: ToSqlRows(PeopleTestSchema,
   177  			MutateRow(PeopleTestSchema, Homer, RatingTag, -8.5),
   178  			MutateRow(PeopleTestSchema, Marge, RatingTag, -8.0),
   179  			MutateRow(PeopleTestSchema, Bart, RatingTag, -9.0),
   180  			MutateRow(PeopleTestSchema, Lisa, RatingTag, -10.0),
   181  			MutateRow(PeopleTestSchema, Moe, RatingTag, -6.5),
   182  			MutateRow(PeopleTestSchema, Barney, RatingTag, -4.0),
   183  		),
   184  		ExpectedSchema: CompressSchema(PeopleTestSchema),
   185  	},
   186  	{
   187  		Name:        "update datetime field",
   188  		UpdateQuery: `update episodes set air_date = "1993-03-24 20:00:00" where id = 1`,
   189  		SelectQuery: `select * from episodes where id = 1`,
   190  		ExpectedRows: ToSqlRows(EpisodesTestSchema,
   191  			MutateRow(EpisodesTestSchema, Ep1, EpAirDateTag, DatetimeStrToTimestamp("1993-03-24 20:00:00")),
   192  		),
   193  		ExpectedSchema: CompressSchema(EpisodesTestSchema),
   194  	},
   195  	{
   196  		Name:        "update datetime field",
   197  		UpdateQuery: `update episodes set name = "fake_name" where id = 1;`,
   198  		SelectQuery: `select * from episodes where id = 1;`,
   199  		ExpectedRows: ToSqlRows(EpisodesTestSchema,
   200  			MutateRow(EpisodesTestSchema, Ep1, EpNameTag, "fake_name"),
   201  		),
   202  		ExpectedSchema: CompressSchema(EpisodesTestSchema),
   203  	},
   204  	{
   205  		Name:        "update multiple rows, =",
   206  		UpdateQuery: `update people set first_name = "Homer" where last_name = "Simpson"`,
   207  		SelectQuery: `select * from people where last_name = "Simpson"`,
   208  		ExpectedRows: ToSqlRows(PeopleTestSchema,
   209  			Homer,
   210  			MutateRow(PeopleTestSchema, Marge, FirstNameTag, "Homer"),
   211  			MutateRow(PeopleTestSchema, Bart, FirstNameTag, "Homer"),
   212  			MutateRow(PeopleTestSchema, Lisa, FirstNameTag, "Homer"),
   213  		),
   214  		ExpectedSchema: CompressSchema(PeopleTestSchema),
   215  	},
   216  	{
   217  		Name:        "update multiple rows, <>",
   218  		UpdateQuery: `update people set last_name = "Simpson" where last_name <> "Simpson"`,
   219  		SelectQuery: `select * from people`,
   220  		ExpectedRows: ToSqlRows(PeopleTestSchema,
   221  			Homer,
   222  			Marge,
   223  			Bart,
   224  			Lisa,
   225  			MutateRow(PeopleTestSchema, Moe, LastNameTag, "Simpson"),
   226  			MutateRow(PeopleTestSchema, Barney, LastNameTag, "Simpson"),
   227  		),
   228  		ExpectedSchema: CompressSchema(PeopleTestSchema),
   229  	},
   230  	{
   231  		Name:        "update multiple rows, >",
   232  		UpdateQuery: `update people set first_name = "Homer" where age > 10`,
   233  		SelectQuery: `select * from people where age > 10`,
   234  		ExpectedRows: ToSqlRows(PeopleTestSchema,
   235  			Homer,
   236  			MutateRow(PeopleTestSchema, Marge, FirstNameTag, "Homer"),
   237  			MutateRow(PeopleTestSchema, Moe, FirstNameTag, "Homer"),
   238  			MutateRow(PeopleTestSchema, Barney, FirstNameTag, "Homer"),
   239  		),
   240  		ExpectedSchema: CompressSchema(PeopleTestSchema),
   241  	},
   242  	{
   243  		Name:        "update multiple rows, >=",
   244  		UpdateQuery: `update people set first_name = "Homer" where age >= 10`,
   245  		SelectQuery: `select * from people where age >= 10`,
   246  		ExpectedRows: ToSqlRows(PeopleTestSchema,
   247  			Homer,
   248  			MutateRow(PeopleTestSchema, Marge, FirstNameTag, "Homer"),
   249  			MutateRow(PeopleTestSchema, Bart, FirstNameTag, "Homer"),
   250  			MutateRow(PeopleTestSchema, Moe, FirstNameTag, "Homer"),
   251  			MutateRow(PeopleTestSchema, Barney, FirstNameTag, "Homer"),
   252  		),
   253  		ExpectedSchema: CompressSchema(PeopleTestSchema),
   254  	},
   255  	{
   256  		Name:        "update multiple rows, <",
   257  		UpdateQuery: `update people set first_name = "Bart" where age < 40`,
   258  		SelectQuery: `select * from people where age < 40`,
   259  		ExpectedRows: ToSqlRows(PeopleTestSchema,
   260  			MutateRow(PeopleTestSchema, Marge, FirstNameTag, "Bart"),
   261  			Bart,
   262  			MutateRow(PeopleTestSchema, Lisa, FirstNameTag, "Bart"),
   263  		),
   264  		ExpectedSchema: CompressSchema(PeopleTestSchema),
   265  	},
   266  	{
   267  		Name:        "update multiple rows, <=",
   268  		UpdateQuery: `update people set first_name = "Homer" where age <= 40`,
   269  		SelectQuery: `select * from people where age <= 40`,
   270  		ExpectedRows: ToSqlRows(PeopleTestSchema,
   271  			Homer,
   272  			MutateRow(PeopleTestSchema, Marge, FirstNameTag, "Homer"),
   273  			MutateRow(PeopleTestSchema, Bart, FirstNameTag, "Homer"),
   274  			MutateRow(PeopleTestSchema, Lisa, FirstNameTag, "Homer"),
   275  			MutateRow(PeopleTestSchema, Barney, FirstNameTag, "Homer"),
   276  		),
   277  		ExpectedSchema: CompressSchema(PeopleTestSchema),
   278  	},
   279  	{
   280  		Name:        "update multiple rows pk increment order by desc",
   281  		UpdateQuery: `update people set id = id + 1 order by id desc`,
   282  		SelectQuery: `select * from people`,
   283  		ExpectedRows: ToSqlRows(PeopleTestSchema,
   284  			MutateRow(PeopleTestSchema, Homer, IdTag, HomerId+1),
   285  			MutateRow(PeopleTestSchema, Marge, IdTag, MargeId+1),
   286  			MutateRow(PeopleTestSchema, Bart, IdTag, BartId+1),
   287  			MutateRow(PeopleTestSchema, Lisa, IdTag, LisaId+1),
   288  			MutateRow(PeopleTestSchema, Moe, IdTag, MoeId+1),
   289  			MutateRow(PeopleTestSchema, Barney, IdTag, BarneyId+1),
   290  		),
   291  		ExpectedSchema: CompressSchema(PeopleTestSchema),
   292  	},
   293  	{
   294  		Name:        "update multiple rows pk increment order by desc",
   295  		UpdateQuery: `update people set id = id + 1 order by id desc`,
   296  		SelectQuery: `select * from people order by id`,
   297  		ExpectedRows: ToSqlRows(PeopleTestSchema,
   298  			MutateRow(PeopleTestSchema, Homer, IdTag, HomerId+1),
   299  			MutateRow(PeopleTestSchema, Marge, IdTag, MargeId+1),
   300  			MutateRow(PeopleTestSchema, Bart, IdTag, BartId+1),
   301  			MutateRow(PeopleTestSchema, Lisa, IdTag, LisaId+1),
   302  			MutateRow(PeopleTestSchema, Moe, IdTag, MoeId+1),
   303  			MutateRow(PeopleTestSchema, Barney, IdTag, BarneyId+1),
   304  		),
   305  		ExpectedSchema: CompressSchema(PeopleTestSchema),
   306  	},
   307  	{
   308  		Name:        "update multiple rows pk increment order by asc",
   309  		UpdateQuery: `update people set id = id + 1 order by id asc`,
   310  		ExpectedErr: "duplicate primary key",
   311  	},
   312  	{
   313  		Name:        "update primary key col",
   314  		UpdateQuery: `update people set id = 0 where first_name = "Marge"`,
   315  		ExpectedErr: "duplicate primary key",
   316  	},
   317  	{
   318  		Name:        "null constraint failure",
   319  		UpdateQuery: `update people set first_name = null where id = 0`,
   320  		ExpectedErr: "Constraint failed for column 'first_name': Not null",
   321  	},
   322  	{
   323  		Name:        "type mismatch list -> string",
   324  		UpdateQuery: `update people set first_name = ("one", "two") where id = 0`,
   325  		ExpectedErr: "Type mismatch",
   326  	},
   327  	{
   328  		Name:        "type mismatch string -> int",
   329  		UpdateQuery: `update people set age = "pretty old" where id = 0`,
   330  		ExpectedErr: "Type mismatch",
   331  	},
   332  	{
   333  		Name:        "type mismatch string -> float",
   334  		UpdateQuery: `update people set rating = "great" where id = 0`,
   335  		ExpectedErr: "Type mismatch",
   336  	},
   337  	{
   338  		Name:        "type mismatch string -> uint",
   339  		UpdateQuery: `update people set num_episodes = "all of them" where id = 0`,
   340  		ExpectedErr: "Type mismatch",
   341  	},
   342  }
   343  
   344  func TestExecuteUpdate(t *testing.T) {
   345  	for _, test := range BasicUpdateTests {
   346  		t.Run(test.Name, func(t *testing.T) {
   347  			testUpdateQuery(t, test)
   348  		})
   349  	}
   350  }
   351  
   352  func TestExecuteUpdateSystemTables(t *testing.T) {
   353  	for _, test := range systemTableUpdateTests {
   354  		t.Run(test.Name, func(t *testing.T) {
   355  			testUpdateQuery(t, test)
   356  		})
   357  	}
   358  }
   359  
   360  var systemTableUpdateTests = []UpdateTest{
   361  	{
   362  		Name: "update dolt_docs",
   363  		AdditionalSetup: CreateTableFn("dolt_docs", doltdb.DocsSchema,
   364  			"INSERT INTO dolt_docs VALUES ('LICENSE.md','A license')"),
   365  		UpdateQuery:    "update dolt_docs set doc_text = 'Some text';",
   366  		SelectQuery:    "select * from dolt_docs",
   367  		ExpectedRows:   []sql.Row{{"LICENSE.md", "Some text"}},
   368  		ExpectedSchema: CompressSchema(doltdb.DocsSchema),
   369  	},
   370  	{
   371  		Name: "update dolt_query_catalog",
   372  		AdditionalSetup: CreateTableFn(doltdb.DoltQueryCatalogTableName, dtables.DoltQueryCatalogSchema,
   373  			"INSERT INTO dolt_query_catalog VALUES ('abc123', 1, 'example', 'select 2+2 from dual', 'description')"),
   374  		UpdateQuery:    "update dolt_query_catalog set display_order = display_order + 1",
   375  		SelectQuery:    "select * from dolt_query_catalog",
   376  		ExpectedRows:   []sql.Row{{"abc123", uint64(2), "example", "select 2+2 from dual", "description"}},
   377  		ExpectedSchema: CompressSchema(dtables.DoltQueryCatalogSchema),
   378  	},
   379  	{
   380  		Name: "update dolt_schemas",
   381  		AdditionalSetup: CreateTableFn(doltdb.SchemasTableName, schemaTableSchema,
   382  			`INSERT INTO dolt_schemas VALUES ('view', 'name', 'create view name as select 2+2 from dual', NULL, NULL)`),
   383  		UpdateQuery:    "update dolt_schemas set type = 'not a view'",
   384  		SelectQuery:    "select * from dolt_schemas",
   385  		ExpectedRows:   []sql.Row{{"not a view", "name", "create view name as select 2+2 from dual", nil, nil}},
   386  		ExpectedSchema: CompressSchema(schemaTableSchema),
   387  	},
   388  }
   389  
   390  // Tests the given query on a freshly created dataset, asserting that the result has the given schema and rows. If
   391  // expectedErr is set, asserts instead that the execution returns an error that matches.
   392  func testUpdateQuery(t *testing.T, test UpdateTest) {
   393  	if (test.ExpectedRows == nil) != (test.ExpectedSchema == nil) {
   394  		require.Fail(t, "Incorrect test setup: schema and rows must both be provided if one is")
   395  	}
   396  
   397  	if len(singleUpdateQueryTest) > 0 && test.Name != singleUpdateQueryTest {
   398  		t.Skip("Skipping tests until " + singleUpdateQueryTest)
   399  	}
   400  
   401  	dEnv, err := CreateTestDatabase()
   402  	require.NoError(t, err)
   403  	defer dEnv.DoltDB.Close()
   404  
   405  	if test.AdditionalSetup != nil {
   406  		test.AdditionalSetup(t, dEnv)
   407  	}
   408  
   409  	root, _ := dEnv.WorkingRoot(context.Background())
   410  	root, err = executeModify(t, context.Background(), dEnv, root, test.UpdateQuery)
   411  	if len(test.ExpectedErr) > 0 {
   412  		require.Error(t, err)
   413  		return
   414  	} else {
   415  		require.NoError(t, err)
   416  	}
   417  
   418  	actualRows, sch, err := executeSelect(t, context.Background(), dEnv, root, test.SelectQuery)
   419  	require.NoError(t, err)
   420  
   421  	assert.Equal(t, len(test.ExpectedRows), len(actualRows))
   422  	for i := 0; i < len(test.ExpectedRows); i++ {
   423  		assert.Equal(t, len(test.ExpectedRows[i]), len(actualRows[i]))
   424  		for j := 0; j < len(test.ExpectedRows[i]); j++ {
   425  			if _, ok := actualRows[i][j].(json.NomsJSON); ok {
   426  				cmp, err := gmstypes.CompareJSON(actualRows[i][j].(json.NomsJSON), test.ExpectedRows[i][j].(json.NomsJSON))
   427  				assert.NoError(t, err)
   428  				assert.Equal(t, 0, cmp)
   429  			} else {
   430  				assert.Equal(t, test.ExpectedRows[i][j], actualRows[i][j])
   431  			}
   432  		}
   433  	}
   434  
   435  	sqlSchema := mustSqlSchema(test.ExpectedSchema)
   436  	assertSchemasEqual(t, sqlSchema, sch)
   437  }