github.com/matrixorigin/matrixone@v0.7.0/pkg/sql/plan/function/operator/compare.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 operator
    16  
    17  import (
    18  	"bytes"
    19  	"github.com/matrixorigin/matrixone/pkg/common/moerr"
    20  	"github.com/matrixorigin/matrixone/pkg/container/nulls"
    21  	"github.com/matrixorigin/matrixone/pkg/container/types"
    22  	"github.com/matrixorigin/matrixone/pkg/container/vector"
    23  	"github.com/matrixorigin/matrixone/pkg/vectorize/compare"
    24  	"github.com/matrixorigin/matrixone/pkg/vm/process"
    25  	"golang.org/x/exp/constraints"
    26  )
    27  
    28  type compareT interface {
    29  	constraints.Integer | constraints.Float | bool |
    30  		types.Date | types.Time | types.Datetime | types.Timestamp
    31  }
    32  
    33  var boolType = types.T_bool.ToType()
    34  
    35  func handleScalarNull(v1, v2 *vector.Vector, proc *process.Process) (*vector.Vector, error) {
    36  	if v1.IsScalarNull() {
    37  		return proc.AllocConstNullVector(boolType, vector.Length(v2)), nil
    38  	} else if v2.IsScalarNull() {
    39  		return proc.AllocConstNullVector(boolType, vector.Length(v1)), nil
    40  	}
    41  	panic(moerr.NewInternalError(proc.Ctx, "handleScalarNull failed."))
    42  }
    43  
    44  func allocateBoolVector(length int, proc *process.Process) *vector.Vector {
    45  	vec, err := proc.AllocVectorOfRows(boolType, int64(length), nil)
    46  	if err != nil {
    47  		panic(moerr.NewOOM(proc.Ctx))
    48  	}
    49  	return vec
    50  }
    51  
    52  type compareFn func(v1, v2, r *vector.Vector) error
    53  
    54  func CompareOrdered(vs []*vector.Vector, proc *process.Process, cfn compareFn) (*vector.Vector, error) {
    55  	left, right := vs[0], vs[1]
    56  
    57  	if left.IsScalarNull() || right.IsScalarNull() {
    58  		return handleScalarNull(left, right, proc)
    59  	}
    60  
    61  	if left.IsScalar() && right.IsScalar() {
    62  		resultVector := proc.AllocScalarVector(boolType)
    63  		if err := cfn(left, right, resultVector); err != nil {
    64  			return nil, err
    65  		}
    66  		return resultVector, nil
    67  	}
    68  
    69  	length := vector.Length(left)
    70  	if left.IsScalar() {
    71  		length = vector.Length(right)
    72  	}
    73  	resultVector := allocateBoolVector(length, proc)
    74  	nulls.Or(left.Nsp, right.Nsp, resultVector.Nsp)
    75  
    76  	if err := cfn(left, right, resultVector); err != nil {
    77  		return nil, err
    78  	}
    79  	return resultVector, nil
    80  }
    81  
    82  // Equal compare operator
    83  func EqGeneral[T compareT](args []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
    84  	return CompareOrdered(args, proc, compare.NumericEqual[T])
    85  }
    86  
    87  func EqDecimal64(args []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
    88  	return CompareOrdered(args, proc, compare.Decimal64VecEq)
    89  }
    90  
    91  func EqDecimal128(args []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
    92  	return CompareOrdered(args, proc, compare.Decimal128VecEq)
    93  }
    94  
    95  // Not Equal compare operator
    96  func NeGeneral[T compareT](args []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
    97  	return CompareOrdered(args, proc, compare.NumericNotEqual[T])
    98  }
    99  
   100  func NeDecimal64(args []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   101  	return CompareOrdered(args, proc, compare.Decimal64VecNe)
   102  }
   103  
   104  func NeDecimal128(args []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   105  	return CompareOrdered(args, proc, compare.Decimal128VecNe)
   106  }
   107  
   108  // IN operator
   109  func INGeneral[T compareT](args []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   110  	leftVec, rightVec := args[0], args[1]
   111  	left, right := vector.MustTCols[T](leftVec), vector.MustTCols[T](rightVec)
   112  	lenLeft := len(left)
   113  	lenRight := len(right)
   114  	if leftVec.IsScalar() {
   115  		lenLeft = 1
   116  	}
   117  	inMap := make(map[T]bool, lenRight)
   118  	for i := 0; i < lenRight; i++ {
   119  		if !rightVec.Nsp.Contains(uint64(i)) {
   120  			inMap[right[i]] = true
   121  		}
   122  	}
   123  	retVec := allocateBoolVector(lenLeft, proc)
   124  	ret := retVec.Col.([]bool)
   125  	for i := 0; i < lenLeft; i++ {
   126  		if _, ok := inMap[left[i]]; ok {
   127  			ret[i] = true
   128  		}
   129  	}
   130  	nulls.Or(leftVec.Nsp, nil, retVec.Nsp)
   131  	return retVec, nil
   132  }
   133  
   134  func INString(args []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   135  	leftVec, rightVec := args[0], args[1]
   136  	left, area1 := vector.MustVarlenaRawData(leftVec)
   137  	right, area2 := vector.MustVarlenaRawData(rightVec)
   138  
   139  	lenLeft := len(left)
   140  	lenRight := len(right)
   141  	if leftVec.IsScalar() {
   142  		lenLeft = 1
   143  	}
   144  	inMap := make(map[string]bool, lenRight)
   145  	for i := 0; i < lenRight; i++ {
   146  		if !rightVec.Nsp.Contains(uint64(i)) {
   147  			inMap[right[i].GetString(area2)] = true
   148  		}
   149  	}
   150  	retVec := allocateBoolVector(lenLeft, proc)
   151  	ret := retVec.Col.([]bool)
   152  	for i := 0; i < lenLeft; i++ {
   153  		if _, ok := inMap[left[i].GetString(area1)]; ok {
   154  			ret[i] = true
   155  		}
   156  	}
   157  	nulls.Or(leftVec.Nsp, nil, retVec.Nsp)
   158  	return retVec, nil
   159  }
   160  
   161  // NOT IN operator
   162  func NotINGeneral[T compareT](args []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   163  	leftVec, rightVec := args[0], args[1]
   164  	left, right := vector.MustTCols[T](leftVec), vector.MustTCols[T](rightVec)
   165  	lenLeft := len(left)
   166  	lenRight := len(right)
   167  	if leftVec.IsScalar() {
   168  		lenLeft = 1
   169  	}
   170  	notInMap := make(map[T]bool, lenRight)
   171  	for i := 0; i < lenRight; i++ {
   172  		if !rightVec.Nsp.Contains(uint64(i)) {
   173  			notInMap[right[i]] = true
   174  		} else {
   175  			//not in null, return false
   176  			return vector.NewConstFixed(boolType, lenLeft, false, proc.Mp()), nil
   177  		}
   178  	}
   179  	retVec := allocateBoolVector(lenLeft, proc)
   180  	ret := retVec.Col.([]bool)
   181  	for i := 0; i < lenLeft; i++ {
   182  		if _, ok := notInMap[left[i]]; !ok {
   183  			ret[i] = true
   184  		}
   185  	}
   186  	nulls.Or(leftVec.Nsp, nil, retVec.Nsp)
   187  	return retVec, nil
   188  }
   189  
   190  func NotINString(args []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   191  	leftVec, rightVec := args[0], args[1]
   192  	left, area1 := vector.MustVarlenaRawData(leftVec)
   193  	right, area2 := vector.MustVarlenaRawData(rightVec)
   194  
   195  	lenLeft := len(left)
   196  	lenRight := len(right)
   197  	if leftVec.IsScalar() {
   198  		lenLeft = 1
   199  	}
   200  	inMap := make(map[string]bool, lenRight)
   201  	for i := 0; i < lenRight; i++ {
   202  		if !rightVec.Nsp.Contains(uint64(i)) {
   203  			inMap[right[i].GetString(area2)] = true
   204  		} else {
   205  			//not in null, return false
   206  			return vector.NewConstFixed(boolType, lenLeft, false, proc.Mp()), nil
   207  		}
   208  	}
   209  	retVec := allocateBoolVector(lenLeft, proc)
   210  	ret := retVec.Col.([]bool)
   211  	for i := 0; i < lenLeft; i++ {
   212  		if _, ok := inMap[left[i].GetString(area1)]; !ok {
   213  			ret[i] = true
   214  		}
   215  	}
   216  	nulls.Or(leftVec.Nsp, nil, retVec.Nsp)
   217  	return retVec, nil
   218  }
   219  
   220  // Great than operator
   221  func GtGeneral[T compareT](args []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   222  	return CompareOrdered(args, proc, compare.NumericGreatThan[T])
   223  }
   224  
   225  func GtDecimal64(args []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   226  	return CompareOrdered(args, proc, compare.Decimal64VecGt)
   227  }
   228  
   229  func GtDecimal128(args []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   230  	return CompareOrdered(args, proc, compare.Decimal128VecGt)
   231  }
   232  
   233  // Great equal operator
   234  func GeGeneral[T compareT](args []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   235  	return CompareOrdered(args, proc, compare.NumericGreatEqual[T])
   236  }
   237  
   238  func GeDecimal64(args []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   239  	return CompareOrdered(args, proc, compare.Decimal64VecGe)
   240  }
   241  
   242  func GeDecimal128(args []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   243  	return CompareOrdered(args, proc, compare.Decimal128VecGe)
   244  }
   245  
   246  // less than operator
   247  func LtGeneral[T compareT](args []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   248  	return CompareOrdered(args, proc, compare.NumericLessThan[T])
   249  }
   250  
   251  func LtDecimal64(args []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   252  	return CompareOrdered(args, proc, compare.Decimal64VecLt)
   253  }
   254  
   255  func LtDecimal128(args []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   256  	return CompareOrdered(args, proc, compare.Decimal128VecLt)
   257  }
   258  
   259  // less equal operator
   260  func LeGeneral[T compareT](args []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   261  	return CompareOrdered(args, proc, compare.NumericLessEqual[T])
   262  }
   263  
   264  func LeDecimal64(args []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   265  	return CompareOrdered(args, proc, compare.Decimal64VecLe)
   266  }
   267  
   268  func LeDecimal128(args []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   269  	return CompareOrdered(args, proc, compare.Decimal128VecLe)
   270  }
   271  
   272  // string compare
   273  type compStringFn func(v1, v2 []byte, s1, s2 int32) bool
   274  
   275  func CompareBytesEq(v1, v2 []byte, s1, s2 int32) bool {
   276  	return bytes.Equal(v1, v2)
   277  }
   278  func CompareBytesLe(v1, v2 []byte, s1, s2 int32) bool {
   279  	return bytes.Compare(v1, v2) <= 0
   280  }
   281  func CompareBytesLt(v1, v2 []byte, s1, s2 int32) bool {
   282  	return bytes.Compare(v1, v2) < 0
   283  }
   284  func CompareBytesGe(v1, v2 []byte, s1, s2 int32) bool {
   285  	return bytes.Compare(v1, v2) >= 0
   286  }
   287  func CompareBytesGt(v1, v2 []byte, s1, s2 int32) bool {
   288  	return bytes.Compare(v1, v2) > 0
   289  }
   290  func CompareBytesNe(v1, v2 []byte, s1, s2 int32) bool {
   291  	return !bytes.Equal(v1, v2)
   292  }
   293  
   294  func CompareValenaInline(vs []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   295  	v1, v2 := vs[0], vs[1]
   296  	if v1.IsScalarNull() || v2.IsScalarNull() {
   297  		return handleScalarNull(v1, v2, proc)
   298  	}
   299  	col1, _ := vector.MustVarlenaRawData(v1)
   300  	col2, _ := vector.MustVarlenaRawData(v2)
   301  
   302  	if v1.IsScalar() && v2.IsScalar() {
   303  		p1 := col1[0].UnsafePtr()
   304  		p2 := col2[0].UnsafePtr()
   305  		ret := *(*[3]int64)(p1) == *(*[3]int64)(p2)
   306  		return vector.NewConstFixed(boolType, 1, ret, proc.Mp()), nil
   307  	}
   308  
   309  	length := vector.Length(v1)
   310  	if length < vector.Length(v2) {
   311  		length = vector.Length(v2)
   312  	}
   313  	vec := allocateBoolVector(length, proc)
   314  	veccol := vec.Col.([]bool)
   315  
   316  	if !v1.IsScalar() && !v2.IsScalar() {
   317  		for i := 0; i < length; i++ {
   318  			p1 := col1[i].UnsafePtr()
   319  			p2 := col2[i].UnsafePtr()
   320  			veccol[i] = *(*[3]int64)(p1) == *(*[3]int64)(p2)
   321  		}
   322  		nulls.Or(v1.Nsp, v2.Nsp, vec.Nsp)
   323  	} else if v1.IsScalar() {
   324  		p1 := col1[0].UnsafePtr()
   325  		for i := 0; i < length; i++ {
   326  			p2 := col2[i].UnsafePtr()
   327  			veccol[i] = *(*[3]int64)(p1) == *(*[3]int64)(p2)
   328  		}
   329  		nulls.Or(nil, v2.Nsp, vec.Nsp)
   330  	} else {
   331  		p2 := col2[0].UnsafePtr()
   332  		for i := 0; i < length; i++ {
   333  			p1 := col1[i].UnsafePtr()
   334  			veccol[i] = *(*[3]int64)(p1) == *(*[3]int64)(p2)
   335  		}
   336  		nulls.Or(v1.Nsp, nil, vec.Nsp)
   337  	}
   338  	return vec, nil
   339  }
   340  
   341  func CompareString(vs []*vector.Vector, fn compStringFn, proc *process.Process) (*vector.Vector, error) {
   342  	v1, v2 := vs[0], vs[1]
   343  
   344  	if v1.IsScalarNull() || v2.IsScalarNull() {
   345  		return handleScalarNull(v1, v2, proc)
   346  	}
   347  
   348  	if v1.IsScalar() && v2.IsScalar() {
   349  		col1, col2 := vector.MustBytesCols(v1), vector.MustBytesCols(v2)
   350  		return vector.NewConstFixed(boolType, 1, fn(col1[0], col2[0], v1.Typ.Width, v2.Typ.Width), proc.Mp()), nil
   351  	}
   352  
   353  	if v1.IsScalar() {
   354  		col1 := vector.MustBytesCols(v1)
   355  		col2, area := vector.MustVarlenaRawData(v2)
   356  		length := vector.Length(v2)
   357  		vec := allocateBoolVector(length, proc)
   358  		veccol := vec.Col.([]bool)
   359  		if v2.GetArea() == nil {
   360  			for i := range veccol {
   361  				veccol[i] = fn(col1[0], (&col2[i]).ByteSlice(), v1.Typ.Width, v2.Typ.Width)
   362  			}
   363  		} else {
   364  			for i := range veccol {
   365  				veccol[i] = fn(col1[0], (&col2[i]).GetByteSlice(area), v1.Typ.Width, v2.Typ.Width)
   366  			}
   367  		}
   368  		nulls.Or(v2.Nsp, nil, vec.Nsp)
   369  		return vec, nil
   370  	}
   371  
   372  	if v2.IsScalar() {
   373  		col1, area := vector.MustVarlenaRawData(v1)
   374  		col2 := vector.MustBytesCols(v2)
   375  		length := vector.Length(v1)
   376  		vec := allocateBoolVector(length, proc)
   377  		veccol := vec.Col.([]bool)
   378  		if v1.GetArea() == nil {
   379  			for i := range veccol {
   380  				veccol[i] = fn((&col1[i]).ByteSlice(), col2[0], v1.Typ.Width, v2.Typ.Width)
   381  			}
   382  		} else {
   383  			for i := range veccol {
   384  				veccol[i] = fn((&col1[i]).GetByteSlice(area), col2[0], v1.Typ.Width, v2.Typ.Width)
   385  			}
   386  		}
   387  		nulls.Or(v1.Nsp, nil, vec.Nsp)
   388  		return vec, nil
   389  	}
   390  
   391  	// Vec Vec
   392  	col1, area1 := vector.MustVarlenaRawData(v1)
   393  	col2, area2 := vector.MustVarlenaRawData(v2)
   394  	length := vector.Length(v1)
   395  	vec := allocateBoolVector(length, proc)
   396  	veccol := vec.Col.([]bool)
   397  	if v1.GetArea() == nil && v2.GetArea() == nil {
   398  		for i := range veccol {
   399  			veccol[i] = fn((&col1[i]).ByteSlice(), (&col2[i]).ByteSlice(), v1.Typ.Width, v2.Typ.Width)
   400  		}
   401  	} else if v1.GetArea() == nil {
   402  		for i := range veccol {
   403  			veccol[i] = fn((&col1[i]).ByteSlice(), (&col2[i]).GetByteSlice(area2), v1.Typ.Width, v2.Typ.Width)
   404  		}
   405  	} else if v2.GetArea() == nil {
   406  		for i := range veccol {
   407  			veccol[i] = fn((&col1[i]).GetByteSlice(area1), (&col2[i]).ByteSlice(), v1.Typ.Width, v2.Typ.Width)
   408  		}
   409  	} else {
   410  		for i := range veccol {
   411  			veccol[i] = fn((&col1[i]).GetByteSlice(area1), (&col2[i]).GetByteSlice(area2), v1.Typ.Width, v2.Typ.Width)
   412  		}
   413  	}
   414  	nulls.Or(v1.Nsp, v2.Nsp, vec.Nsp)
   415  	return vec, nil
   416  }
   417  
   418  func EqString(vs []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   419  	if vs[0].GetArea() == nil && vs[1].GetArea() == nil {
   420  		return CompareValenaInline(vs, proc)
   421  	}
   422  	return CompareString(vs, CompareBytesEq, proc)
   423  }
   424  
   425  func LeString(vs []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   426  	return CompareString(vs, CompareBytesLe, proc)
   427  }
   428  
   429  func LtString(vs []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   430  	return CompareString(vs, CompareBytesLt, proc)
   431  }
   432  
   433  func GeString(vs []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   434  	return CompareString(vs, CompareBytesGe, proc)
   435  }
   436  
   437  func GtString(vs []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   438  	return CompareString(vs, CompareBytesGt, proc)
   439  }
   440  
   441  func NeString(vs []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   442  	return CompareString(vs, CompareBytesNe, proc)
   443  }
   444  
   445  // uuid compare
   446  type compUuidFn func(v1, v2 [16]byte) bool
   447  
   448  func CompareUuidEq(v1, v2 [16]byte) bool {
   449  	return types.EqualUuid(v1, v2)
   450  }
   451  func CompareUuidLe(v1, v2 [16]byte) bool {
   452  	return types.CompareUuid(v1, v2) <= 0
   453  }
   454  func CompareUuidLt(v1, v2 [16]byte) bool {
   455  	return types.CompareUuid(v1, v2) < 0
   456  }
   457  func CompareUuidGe(v1, v2 [16]byte) bool {
   458  	return types.CompareUuid(v1, v2) >= 0
   459  }
   460  func CompareUuidGt(v1, v2 [16]byte) bool {
   461  	return types.CompareUuid(v1, v2) > 0
   462  }
   463  func CompareUuidNe(v1, v2 [16]byte) bool {
   464  	return !types.EqualUuid(v1, v2)
   465  }
   466  
   467  func CompareUuid(vs []*vector.Vector, fn compUuidFn, proc *process.Process) (*vector.Vector, error) {
   468  	v1, v2 := vs[0], vs[1]
   469  	//col1, col2 := vector.MustBytesCols(v1), vector.MustBytesCols(v2)
   470  	col1, col2 := vector.MustTCols[types.Uuid](v1), vector.MustTCols[types.Uuid](v2)
   471  	if v1.IsScalarNull() || v2.IsScalarNull() {
   472  		return handleScalarNull(v1, v2, proc)
   473  	}
   474  
   475  	if v1.IsScalar() && v2.IsScalar() {
   476  		return vector.NewConstFixed(boolType, 1, fn(col1[0], col2[0]), proc.Mp()), nil
   477  	}
   478  
   479  	if v1.IsScalar() {
   480  		length := vector.Length(v2)
   481  		vec := allocateBoolVector(length, proc)
   482  		veccol := vec.Col.([]bool)
   483  		for i := range veccol {
   484  			veccol[i] = fn(col1[0], col2[i])
   485  		}
   486  		nulls.Or(v2.Nsp, nil, vec.Nsp)
   487  		return vec, nil
   488  	}
   489  
   490  	if v2.IsScalar() {
   491  		length := vector.Length(v1)
   492  		vec := allocateBoolVector(length, proc)
   493  		veccol := vec.Col.([]bool)
   494  		for i := range veccol {
   495  			veccol[i] = fn(col1[i], col2[0])
   496  		}
   497  		nulls.Or(v1.Nsp, nil, vec.Nsp)
   498  		return vec, nil
   499  	}
   500  
   501  	// Vec Vec
   502  	length := vector.Length(v1)
   503  	vec := allocateBoolVector(length, proc)
   504  	veccol := vec.Col.([]bool)
   505  	for i := range veccol {
   506  		veccol[i] = fn(col1[i], col2[i])
   507  	}
   508  	nulls.Or(v1.Nsp, v2.Nsp, vec.Nsp)
   509  	return vec, nil
   510  }
   511  
   512  func EqUuid(vs []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   513  	return CompareUuid(vs, CompareUuidEq, proc)
   514  }
   515  
   516  func LeUuid(vs []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   517  	return CompareUuid(vs, CompareUuidLe, proc)
   518  }
   519  
   520  func LtUuid(vs []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   521  	return CompareUuid(vs, CompareUuidLt, proc)
   522  }
   523  
   524  func GeUuid(vs []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   525  	return CompareUuid(vs, CompareUuidGe, proc)
   526  }
   527  
   528  func GtUuid(vs []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   529  	return CompareUuid(vs, CompareUuidGt, proc)
   530  }
   531  
   532  func NeUuid(vs []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
   533  	return CompareUuid(vs, CompareUuidNe, proc)
   534  }