github.com/hdt3213/godis@v1.2.9/database/sortedset.go (about)

     1  package database
     2  
     3  import (
     4  	SortedSet "github.com/hdt3213/godis/datastruct/sortedset"
     5  	"github.com/hdt3213/godis/interface/database"
     6  	"github.com/hdt3213/godis/interface/redis"
     7  	"github.com/hdt3213/godis/lib/utils"
     8  	"github.com/hdt3213/godis/redis/protocol"
     9  	"strconv"
    10  	"strings"
    11  )
    12  
    13  func (db *DB) getAsSortedSet(key string) (*SortedSet.SortedSet, protocol.ErrorReply) {
    14  	entity, exists := db.GetEntity(key)
    15  	if !exists {
    16  		return nil, nil
    17  	}
    18  	sortedSet, ok := entity.Data.(*SortedSet.SortedSet)
    19  	if !ok {
    20  		return nil, &protocol.WrongTypeErrReply{}
    21  	}
    22  	return sortedSet, nil
    23  }
    24  
    25  func (db *DB) getOrInitSortedSet(key string) (sortedSet *SortedSet.SortedSet, inited bool, errReply protocol.ErrorReply) {
    26  	sortedSet, errReply = db.getAsSortedSet(key)
    27  	if errReply != nil {
    28  		return nil, false, errReply
    29  	}
    30  	inited = false
    31  	if sortedSet == nil {
    32  		sortedSet = SortedSet.Make()
    33  		db.PutEntity(key, &database.DataEntity{
    34  			Data: sortedSet,
    35  		})
    36  		inited = true
    37  	}
    38  	return sortedSet, inited, nil
    39  }
    40  
    41  // execZAdd adds member into sorted set
    42  func execZAdd(db *DB, args [][]byte) redis.Reply {
    43  	if len(args)%2 != 1 {
    44  		return protocol.MakeSyntaxErrReply()
    45  	}
    46  	key := string(args[0])
    47  	size := (len(args) - 1) / 2
    48  	elements := make([]*SortedSet.Element, size)
    49  	for i := 0; i < size; i++ {
    50  		scoreValue := args[2*i+1]
    51  		member := string(args[2*i+2])
    52  		score, err := strconv.ParseFloat(string(scoreValue), 64)
    53  		if err != nil {
    54  			return protocol.MakeErrReply("ERR value is not a valid float")
    55  		}
    56  		elements[i] = &SortedSet.Element{
    57  			Member: member,
    58  			Score:  score,
    59  		}
    60  	}
    61  
    62  	// get or init entity
    63  	sortedSet, _, errReply := db.getOrInitSortedSet(key)
    64  	if errReply != nil {
    65  		return errReply
    66  	}
    67  
    68  	i := 0
    69  	for _, e := range elements {
    70  		if sortedSet.Add(e.Member, e.Score) {
    71  			i++
    72  		}
    73  	}
    74  
    75  	db.addAof(utils.ToCmdLine3("zadd", args...))
    76  
    77  	return protocol.MakeIntReply(int64(i))
    78  }
    79  
    80  func undoZAdd(db *DB, args [][]byte) []CmdLine {
    81  	key := string(args[0])
    82  	size := (len(args) - 1) / 2
    83  	fields := make([]string, size)
    84  	for i := 0; i < size; i++ {
    85  		fields[i] = string(args[2*i+2])
    86  	}
    87  	return rollbackZSetFields(db, key, fields...)
    88  }
    89  
    90  // execZScore gets score of a member in sortedset
    91  func execZScore(db *DB, args [][]byte) redis.Reply {
    92  	// parse args
    93  	key := string(args[0])
    94  	member := string(args[1])
    95  
    96  	sortedSet, errReply := db.getAsSortedSet(key)
    97  	if errReply != nil {
    98  		return errReply
    99  	}
   100  	if sortedSet == nil {
   101  		return &protocol.NullBulkReply{}
   102  	}
   103  
   104  	element, exists := sortedSet.Get(member)
   105  	if !exists {
   106  		return &protocol.NullBulkReply{}
   107  	}
   108  	value := strconv.FormatFloat(element.Score, 'f', -1, 64)
   109  	return protocol.MakeBulkReply([]byte(value))
   110  }
   111  
   112  // execZRank gets index of a member in sortedset, ascending order, start from 0
   113  func execZRank(db *DB, args [][]byte) redis.Reply {
   114  	// parse args
   115  	key := string(args[0])
   116  	member := string(args[1])
   117  
   118  	// get entity
   119  	sortedSet, errReply := db.getAsSortedSet(key)
   120  	if errReply != nil {
   121  		return errReply
   122  	}
   123  	if sortedSet == nil {
   124  		return &protocol.NullBulkReply{}
   125  	}
   126  
   127  	rank := sortedSet.GetRank(member, false)
   128  	if rank < 0 {
   129  		return &protocol.NullBulkReply{}
   130  	}
   131  	return protocol.MakeIntReply(rank)
   132  }
   133  
   134  // execZRevRank gets index of a member in sortedset, descending order, start from 0
   135  func execZRevRank(db *DB, args [][]byte) redis.Reply {
   136  	// parse args
   137  	key := string(args[0])
   138  	member := string(args[1])
   139  
   140  	// get entity
   141  	sortedSet, errReply := db.getAsSortedSet(key)
   142  	if errReply != nil {
   143  		return errReply
   144  	}
   145  	if sortedSet == nil {
   146  		return &protocol.NullBulkReply{}
   147  	}
   148  
   149  	rank := sortedSet.GetRank(member, true)
   150  	if rank < 0 {
   151  		return &protocol.NullBulkReply{}
   152  	}
   153  	return protocol.MakeIntReply(rank)
   154  }
   155  
   156  // execZCard gets number of members in sortedset
   157  func execZCard(db *DB, args [][]byte) redis.Reply {
   158  	// parse args
   159  	key := string(args[0])
   160  
   161  	// get entity
   162  	sortedSet, errReply := db.getAsSortedSet(key)
   163  	if errReply != nil {
   164  		return errReply
   165  	}
   166  	if sortedSet == nil {
   167  		return protocol.MakeIntReply(0)
   168  	}
   169  
   170  	return protocol.MakeIntReply(sortedSet.Len())
   171  }
   172  
   173  // execZRange gets members in range, sort by score in ascending order
   174  func execZRange(db *DB, args [][]byte) redis.Reply {
   175  	// parse args
   176  	if len(args) != 3 && len(args) != 4 {
   177  		return protocol.MakeErrReply("ERR wrong number of arguments for 'zrange' command")
   178  	}
   179  	withScores := false
   180  	if len(args) == 4 {
   181  		if strings.ToUpper(string(args[3])) != "WITHSCORES" {
   182  			return protocol.MakeErrReply("syntax error")
   183  		}
   184  		withScores = true
   185  	}
   186  	key := string(args[0])
   187  	start, err := strconv.ParseInt(string(args[1]), 10, 64)
   188  	if err != nil {
   189  		return protocol.MakeErrReply("ERR value is not an integer or out of range")
   190  	}
   191  	stop, err := strconv.ParseInt(string(args[2]), 10, 64)
   192  	if err != nil {
   193  		return protocol.MakeErrReply("ERR value is not an integer or out of range")
   194  	}
   195  	return range0(db, key, start, stop, withScores, false)
   196  }
   197  
   198  // execZRevRange gets members in range, sort by score in descending order
   199  func execZRevRange(db *DB, args [][]byte) redis.Reply {
   200  	// parse args
   201  	if len(args) != 3 && len(args) != 4 {
   202  		return protocol.MakeErrReply("ERR wrong number of arguments for 'zrevrange' command")
   203  	}
   204  	withScores := false
   205  	if len(args) == 4 {
   206  		if string(args[3]) != "WITHSCORES" {
   207  			return protocol.MakeErrReply("syntax error")
   208  		}
   209  		withScores = true
   210  	}
   211  	key := string(args[0])
   212  	start, err := strconv.ParseInt(string(args[1]), 10, 64)
   213  	if err != nil {
   214  		return protocol.MakeErrReply("ERR value is not an integer or out of range")
   215  	}
   216  	stop, err := strconv.ParseInt(string(args[2]), 10, 64)
   217  	if err != nil {
   218  		return protocol.MakeErrReply("ERR value is not an integer or out of range")
   219  	}
   220  	return range0(db, key, start, stop, withScores, true)
   221  }
   222  
   223  func range0(db *DB, key string, start int64, stop int64, withScores bool, desc bool) redis.Reply {
   224  	// get data
   225  	sortedSet, errReply := db.getAsSortedSet(key)
   226  	if errReply != nil {
   227  		return errReply
   228  	}
   229  	if sortedSet == nil {
   230  		return &protocol.EmptyMultiBulkReply{}
   231  	}
   232  
   233  	// compute index
   234  	size := sortedSet.Len() // assert: size > 0
   235  	if start < -1*size {
   236  		start = 0
   237  	} else if start < 0 {
   238  		start = size + start
   239  	} else if start >= size {
   240  		return &protocol.EmptyMultiBulkReply{}
   241  	}
   242  	if stop < -1*size {
   243  		stop = 0
   244  	} else if stop < 0 {
   245  		stop = size + stop + 1
   246  	} else if stop < size {
   247  		stop = stop + 1
   248  	} else {
   249  		stop = size
   250  	}
   251  	if stop < start {
   252  		stop = start
   253  	}
   254  
   255  	// assert: start in [0, size - 1], stop in [start, size]
   256  	slice := sortedSet.Range(start, stop, desc)
   257  	if withScores {
   258  		result := make([][]byte, len(slice)*2)
   259  		i := 0
   260  		for _, element := range slice {
   261  			result[i] = []byte(element.Member)
   262  			i++
   263  			scoreStr := strconv.FormatFloat(element.Score, 'f', -1, 64)
   264  			result[i] = []byte(scoreStr)
   265  			i++
   266  		}
   267  		return protocol.MakeMultiBulkReply(result)
   268  	}
   269  	result := make([][]byte, len(slice))
   270  	i := 0
   271  	for _, element := range slice {
   272  		result[i] = []byte(element.Member)
   273  		i++
   274  	}
   275  	return protocol.MakeMultiBulkReply(result)
   276  }
   277  
   278  // execZCount gets number of members which score within given range
   279  func execZCount(db *DB, args [][]byte) redis.Reply {
   280  	key := string(args[0])
   281  
   282  	min, err := SortedSet.ParseScoreBorder(string(args[1]))
   283  	if err != nil {
   284  		return protocol.MakeErrReply(err.Error())
   285  	}
   286  
   287  	max, err := SortedSet.ParseScoreBorder(string(args[2]))
   288  	if err != nil {
   289  		return protocol.MakeErrReply(err.Error())
   290  	}
   291  
   292  	// get data
   293  	sortedSet, errReply := db.getAsSortedSet(key)
   294  	if errReply != nil {
   295  		return errReply
   296  	}
   297  	if sortedSet == nil {
   298  		return protocol.MakeIntReply(0)
   299  	}
   300  
   301  	return protocol.MakeIntReply(sortedSet.Count(min, max))
   302  }
   303  
   304  /*
   305   * param limit: limit < 0 means no limit
   306   */
   307  func rangeByScore0(db *DB, key string, min *SortedSet.ScoreBorder, max *SortedSet.ScoreBorder, offset int64, limit int64, withScores bool, desc bool) redis.Reply {
   308  	// get data
   309  	sortedSet, errReply := db.getAsSortedSet(key)
   310  	if errReply != nil {
   311  		return errReply
   312  	}
   313  	if sortedSet == nil {
   314  		return &protocol.EmptyMultiBulkReply{}
   315  	}
   316  
   317  	slice := sortedSet.RangeByScore(min, max, offset, limit, desc)
   318  	if withScores {
   319  		result := make([][]byte, len(slice)*2)
   320  		i := 0
   321  		for _, element := range slice {
   322  			result[i] = []byte(element.Member)
   323  			i++
   324  			scoreStr := strconv.FormatFloat(element.Score, 'f', -1, 64)
   325  			result[i] = []byte(scoreStr)
   326  			i++
   327  		}
   328  		return protocol.MakeMultiBulkReply(result)
   329  	}
   330  	result := make([][]byte, len(slice))
   331  	i := 0
   332  	for _, element := range slice {
   333  		result[i] = []byte(element.Member)
   334  		i++
   335  	}
   336  	return protocol.MakeMultiBulkReply(result)
   337  }
   338  
   339  // execZRangeByScore gets members which score within given range, in ascending order
   340  func execZRangeByScore(db *DB, args [][]byte) redis.Reply {
   341  	if len(args) < 3 {
   342  		return protocol.MakeErrReply("ERR wrong number of arguments for 'zrangebyscore' command")
   343  	}
   344  	key := string(args[0])
   345  
   346  	min, err := SortedSet.ParseScoreBorder(string(args[1]))
   347  	if err != nil {
   348  		return protocol.MakeErrReply(err.Error())
   349  	}
   350  
   351  	max, err := SortedSet.ParseScoreBorder(string(args[2]))
   352  	if err != nil {
   353  		return protocol.MakeErrReply(err.Error())
   354  	}
   355  
   356  	withScores := false
   357  	var offset int64 = 0
   358  	var limit int64 = -1
   359  	if len(args) > 3 {
   360  		for i := 3; i < len(args); {
   361  			s := string(args[i])
   362  			if strings.ToUpper(s) == "WITHSCORES" {
   363  				withScores = true
   364  				i++
   365  			} else if strings.ToUpper(s) == "LIMIT" {
   366  				if len(args) < i+3 {
   367  					return protocol.MakeErrReply("ERR syntax error")
   368  				}
   369  				offset, err = strconv.ParseInt(string(args[i+1]), 10, 64)
   370  				if err != nil {
   371  					return protocol.MakeErrReply("ERR value is not an integer or out of range")
   372  				}
   373  				limit, err = strconv.ParseInt(string(args[i+2]), 10, 64)
   374  				if err != nil {
   375  					return protocol.MakeErrReply("ERR value is not an integer or out of range")
   376  				}
   377  				i += 3
   378  			} else {
   379  				return protocol.MakeErrReply("ERR syntax error")
   380  			}
   381  		}
   382  	}
   383  	return rangeByScore0(db, key, min, max, offset, limit, withScores, false)
   384  }
   385  
   386  // execZRevRangeByScore gets number of members which score within given range, in descending order
   387  func execZRevRangeByScore(db *DB, args [][]byte) redis.Reply {
   388  	if len(args) < 3 {
   389  		return protocol.MakeErrReply("ERR wrong number of arguments for 'zrangebyscore' command")
   390  	}
   391  	key := string(args[0])
   392  
   393  	min, err := SortedSet.ParseScoreBorder(string(args[2]))
   394  	if err != nil {
   395  		return protocol.MakeErrReply(err.Error())
   396  	}
   397  
   398  	max, err := SortedSet.ParseScoreBorder(string(args[1]))
   399  	if err != nil {
   400  		return protocol.MakeErrReply(err.Error())
   401  	}
   402  
   403  	withScores := false
   404  	var offset int64 = 0
   405  	var limit int64 = -1
   406  	if len(args) > 3 {
   407  		for i := 3; i < len(args); {
   408  			s := string(args[i])
   409  			if strings.ToUpper(s) == "WITHSCORES" {
   410  				withScores = true
   411  				i++
   412  			} else if strings.ToUpper(s) == "LIMIT" {
   413  				if len(args) < i+3 {
   414  					return protocol.MakeErrReply("ERR syntax error")
   415  				}
   416  				offset, err = strconv.ParseInt(string(args[i+1]), 10, 64)
   417  				if err != nil {
   418  					return protocol.MakeErrReply("ERR value is not an integer or out of range")
   419  				}
   420  				limit, err = strconv.ParseInt(string(args[i+2]), 10, 64)
   421  				if err != nil {
   422  					return protocol.MakeErrReply("ERR value is not an integer or out of range")
   423  				}
   424  				i += 3
   425  			} else {
   426  				return protocol.MakeErrReply("ERR syntax error")
   427  			}
   428  		}
   429  	}
   430  	return rangeByScore0(db, key, min, max, offset, limit, withScores, true)
   431  }
   432  
   433  // execZRemRangeByScore removes members which score within given range
   434  func execZRemRangeByScore(db *DB, args [][]byte) redis.Reply {
   435  	if len(args) != 3 {
   436  		return protocol.MakeErrReply("ERR wrong number of arguments for 'zremrangebyscore' command")
   437  	}
   438  	key := string(args[0])
   439  
   440  	min, err := SortedSet.ParseScoreBorder(string(args[1]))
   441  	if err != nil {
   442  		return protocol.MakeErrReply(err.Error())
   443  	}
   444  
   445  	max, err := SortedSet.ParseScoreBorder(string(args[2]))
   446  	if err != nil {
   447  		return protocol.MakeErrReply(err.Error())
   448  	}
   449  
   450  	// get data
   451  	sortedSet, errReply := db.getAsSortedSet(key)
   452  	if errReply != nil {
   453  		return errReply
   454  	}
   455  	if sortedSet == nil {
   456  		return &protocol.EmptyMultiBulkReply{}
   457  	}
   458  
   459  	removed := sortedSet.RemoveByScore(min, max)
   460  	if removed > 0 {
   461  		db.addAof(utils.ToCmdLine3("zremrangebyscore", args...))
   462  	}
   463  	return protocol.MakeIntReply(removed)
   464  }
   465  
   466  // execZRemRangeByRank removes members within given indexes
   467  func execZRemRangeByRank(db *DB, args [][]byte) redis.Reply {
   468  	key := string(args[0])
   469  	start, err := strconv.ParseInt(string(args[1]), 10, 64)
   470  	if err != nil {
   471  		return protocol.MakeErrReply("ERR value is not an integer or out of range")
   472  	}
   473  	stop, err := strconv.ParseInt(string(args[2]), 10, 64)
   474  	if err != nil {
   475  		return protocol.MakeErrReply("ERR value is not an integer or out of range")
   476  	}
   477  
   478  	// get data
   479  	sortedSet, errReply := db.getAsSortedSet(key)
   480  	if errReply != nil {
   481  		return errReply
   482  	}
   483  	if sortedSet == nil {
   484  		return protocol.MakeIntReply(0)
   485  	}
   486  
   487  	// compute index
   488  	size := sortedSet.Len() // assert: size > 0
   489  	if start < -1*size {
   490  		start = 0
   491  	} else if start < 0 {
   492  		start = size + start
   493  	} else if start >= size {
   494  		return protocol.MakeIntReply(0)
   495  	}
   496  	if stop < -1*size {
   497  		stop = 0
   498  	} else if stop < 0 {
   499  		stop = size + stop + 1
   500  	} else if stop < size {
   501  		stop = stop + 1
   502  	} else {
   503  		stop = size
   504  	}
   505  	if stop < start {
   506  		stop = start
   507  	}
   508  
   509  	// assert: start in [0, size - 1], stop in [start, size]
   510  	removed := sortedSet.RemoveByRank(start, stop)
   511  	if removed > 0 {
   512  		db.addAof(utils.ToCmdLine3("zremrangebyrank", args...))
   513  	}
   514  	return protocol.MakeIntReply(removed)
   515  }
   516  
   517  func execZPopMin(db *DB, args [][]byte) redis.Reply {
   518  	key := string(args[0])
   519  	count := 1
   520  	if len(args) > 1 {
   521  		var err error
   522  		count, err = strconv.Atoi(string(args[1]))
   523  		if err != nil {
   524  			return protocol.MakeErrReply("ERR value is not an integer or out of range")
   525  		}
   526  	}
   527  
   528  	sortedSet, errReply := db.getAsSortedSet(key)
   529  	if errReply != nil {
   530  		return errReply
   531  	}
   532  	if sortedSet == nil {
   533  		return protocol.MakeEmptyMultiBulkReply()
   534  	}
   535  
   536  	removed := sortedSet.PopMin(count)
   537  	if len(removed) > 0 {
   538  		db.addAof(utils.ToCmdLine3("zpopmin", args...))
   539  	}
   540  	result := make([][]byte, 0, len(removed)*2)
   541  	for _, element := range removed {
   542  		scoreStr := strconv.FormatFloat(element.Score, 'f', -1, 64)
   543  		result = append(result, []byte(element.Member), []byte(scoreStr))
   544  	}
   545  	return protocol.MakeMultiBulkReply(result)
   546  }
   547  
   548  // execZRem removes given members
   549  func execZRem(db *DB, args [][]byte) redis.Reply {
   550  	// parse args
   551  	key := string(args[0])
   552  	fields := make([]string, len(args)-1)
   553  	fieldArgs := args[1:]
   554  	for i, v := range fieldArgs {
   555  		fields[i] = string(v)
   556  	}
   557  
   558  	// get entity
   559  	sortedSet, errReply := db.getAsSortedSet(key)
   560  	if errReply != nil {
   561  		return errReply
   562  	}
   563  	if sortedSet == nil {
   564  		return protocol.MakeIntReply(0)
   565  	}
   566  
   567  	var deleted int64 = 0
   568  	for _, field := range fields {
   569  		if sortedSet.Remove(field) {
   570  			deleted++
   571  		}
   572  	}
   573  	if deleted > 0 {
   574  		db.addAof(utils.ToCmdLine3("zrem", args...))
   575  	}
   576  	return protocol.MakeIntReply(deleted)
   577  }
   578  
   579  func undoZRem(db *DB, args [][]byte) []CmdLine {
   580  	key := string(args[0])
   581  	fields := make([]string, len(args)-1)
   582  	fieldArgs := args[1:]
   583  	for i, v := range fieldArgs {
   584  		fields[i] = string(v)
   585  	}
   586  	return rollbackZSetFields(db, key, fields...)
   587  }
   588  
   589  // execZIncrBy increments the score of a member
   590  func execZIncrBy(db *DB, args [][]byte) redis.Reply {
   591  	key := string(args[0])
   592  	rawDelta := string(args[1])
   593  	field := string(args[2])
   594  	delta, err := strconv.ParseFloat(rawDelta, 64)
   595  	if err != nil {
   596  		return protocol.MakeErrReply("ERR value is not a valid float")
   597  	}
   598  
   599  	// get or init entity
   600  	sortedSet, _, errReply := db.getOrInitSortedSet(key)
   601  	if errReply != nil {
   602  		return errReply
   603  	}
   604  
   605  	element, exists := sortedSet.Get(field)
   606  	if !exists {
   607  		sortedSet.Add(field, delta)
   608  		db.addAof(utils.ToCmdLine3("zincrby", args...))
   609  		return protocol.MakeBulkReply(args[1])
   610  	}
   611  	score := element.Score + delta
   612  	sortedSet.Add(field, score)
   613  	bytes := []byte(strconv.FormatFloat(score, 'f', -1, 64))
   614  	db.addAof(utils.ToCmdLine3("zincrby", args...))
   615  	return protocol.MakeBulkReply(bytes)
   616  }
   617  
   618  func undoZIncr(db *DB, args [][]byte) []CmdLine {
   619  	key := string(args[0])
   620  	field := string(args[2])
   621  	return rollbackZSetFields(db, key, field)
   622  }
   623  
   624  func init() {
   625  	registerCommand("ZAdd", execZAdd, writeFirstKey, undoZAdd, -4, flagWrite).
   626  		attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM, redisFlagFast}, 1, 1, 1)
   627  	registerCommand("ZScore", execZScore, readFirstKey, nil, 3, flagReadOnly).
   628  		attachCommandExtra([]string{redisFlagReadonly, redisFlagFast}, 1, 1, 1)
   629  	registerCommand("ZIncrBy", execZIncrBy, writeFirstKey, undoZIncr, 4, flagWrite).
   630  		attachCommandExtra([]string{redisFlagWrite, redisFlagDenyOOM, redisFlagFast}, 1, 1, 1)
   631  	registerCommand("ZRank", execZRank, readFirstKey, nil, 3, flagReadOnly).
   632  		attachCommandExtra([]string{redisFlagReadonly, redisFlagFast}, 1, 1, 1)
   633  	registerCommand("ZCount", execZCount, readFirstKey, nil, 4, flagReadOnly).
   634  		attachCommandExtra([]string{redisFlagReadonly, redisFlagFast}, 1, 1, 1)
   635  	registerCommand("ZRevRank", execZRevRank, readFirstKey, nil, 3, flagReadOnly).
   636  		attachCommandExtra([]string{redisFlagReadonly, redisFlagFast}, 1, 1, 1)
   637  	registerCommand("ZCard", execZCard, readFirstKey, nil, 2, flagReadOnly).
   638  		attachCommandExtra([]string{redisFlagReadonly, redisFlagFast}, 1, 1, 1)
   639  	registerCommand("ZRange", execZRange, readFirstKey, nil, -4, flagReadOnly).
   640  		attachCommandExtra([]string{redisFlagReadonly}, 1, 1, 1)
   641  	registerCommand("ZRangeByScore", execZRangeByScore, readFirstKey, nil, -4, flagReadOnly).
   642  		attachCommandExtra([]string{redisFlagReadonly}, 1, 1, 1)
   643  	registerCommand("ZRevRange", execZRevRange, readFirstKey, nil, -4, flagReadOnly).
   644  		attachCommandExtra([]string{redisFlagReadonly}, 1, 1, 1)
   645  	registerCommand("ZRevRangeByScore", execZRevRangeByScore, readFirstKey, nil, -4, flagReadOnly).
   646  		attachCommandExtra([]string{redisFlagReadonly}, 1, 1, 1)
   647  	registerCommand("ZPopMin", execZPopMin, writeFirstKey, rollbackFirstKey, -2, flagWrite).
   648  		attachCommandExtra([]string{redisFlagWrite, redisFlagFast}, 1, 1, 1)
   649  	registerCommand("ZRem", execZRem, writeFirstKey, undoZRem, -3, flagWrite).
   650  		attachCommandExtra([]string{redisFlagWrite, redisFlagFast}, 1, 1, 1)
   651  	registerCommand("ZRemRangeByScore", execZRemRangeByScore, writeFirstKey, rollbackFirstKey, 4, flagWrite).
   652  		attachCommandExtra([]string{redisFlagWrite}, 1, 1, 1)
   653  	registerCommand("ZRemRangeByRank", execZRemRangeByRank, writeFirstKey, rollbackFirstKey, 4, flagWrite).
   654  		attachCommandExtra([]string{redisFlagWrite}, 1, 1, 1)
   655  }