summaryrefslogtreecommitdiffstats
path: root/tests/unit/sort.tcl
blob: 109f6615e9ba7eb4be35ec242ab5bc5bdd94f7f5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
start_server {
    tags {"sort"}
    overrides {
        "list-max-ziplist-size" 16
        "set-max-intset-entries" 32
    }
} {
    proc create_random_dataset {num cmd} {
        set tosort {}
        set result {}
        array set seenrand {}
        r del tosort
        for {set i 0} {$i < $num} {incr i} {
            # Make sure all the weights are different because
            # Redis does not use a stable sort but Tcl does.
            while 1 {
                randpath {
                    set rint [expr int(rand()*1000000)]
                } {
                    set rint [expr rand()]
                }
                if {![info exists seenrand($rint)]} break
            }
            set seenrand($rint) x
            r $cmd tosort $i
            r set weight_$i $rint
            r hset wobj_$i weight $rint
            lappend tosort [list $i $rint]
        }
        set sorted [lsort -index 1 -real $tosort]
        for {set i 0} {$i < $num} {incr i} {
            lappend result [lindex $sorted $i 0]
        }
        set _ $result
    }

    proc check_sort_store_encoding {key} {
        set listpack_max_size [lindex [r config get list-max-ziplist-size] 1]

        # When the length or size of quicklist is less than the limit,
        # it will be converted to listpack.
        if {[r llen $key] <= $listpack_max_size} {
            assert_encoding listpack $key
        } else {
            assert_encoding quicklist $key
        }
    }

    foreach {num cmd enc title} {
        16 lpush listpack "Listpack"
        1000 lpush quicklist "Quicklist"
        10000 lpush quicklist "Big Quicklist"
        16 sadd intset "Intset"
        1000 sadd hashtable "Hash table"
        10000 sadd hashtable "Big Hash table"
    } {
        set result [create_random_dataset $num $cmd]
        assert_encoding $enc tosort

        test "$title: SORT BY key" {
            assert_equal $result [r sort tosort BY weight_*]
        } {} {cluster:skip}

        test "$title: SORT BY key with limit" {
            assert_equal [lrange $result 5 9] [r sort tosort BY weight_* LIMIT 5 5]
        } {} {cluster:skip}

        test "$title: SORT BY hash field" {
            assert_equal $result [r sort tosort BY wobj_*->weight]
        } {} {cluster:skip}
    }

    set result [create_random_dataset 16 lpush]
    test "SORT GET #" {
        assert_equal [lsort -integer $result] [r sort tosort GET #]
    } {} {cluster:skip}

foreach command {SORT SORT_RO} {
    test "$command GET <const>" {
        r del foo
        set res [r $command tosort GET foo]
        assert_equal 16 [llength $res]
        foreach item $res { assert_equal {} $item }
    } {} {cluster:skip}
}

    test "SORT GET (key and hash) with sanity check" {
        set l1 [r sort tosort GET # GET weight_*]
        set l2 [r sort tosort GET # GET wobj_*->weight]
        foreach {id1 w1} $l1 {id2 w2} $l2 {
            assert_equal $id1 $id2
            assert_equal $w1 [r get weight_$id1]
            assert_equal $w2 [r get weight_$id1]
        }
    } {} {cluster:skip}

    test "SORT BY key STORE" {
        r sort tosort BY weight_* store sort-res
        assert_equal $result [r lrange sort-res 0 -1]
        assert_equal 16 [r llen sort-res]
        check_sort_store_encoding sort-res
    } {} {cluster:skip}

    test "SORT BY hash field STORE" {
        r sort tosort BY wobj_*->weight store sort-res
        assert_equal $result [r lrange sort-res 0 -1]
        assert_equal 16 [r llen sort-res]
        check_sort_store_encoding sort-res
    } {} {cluster:skip}

    test "SORT extracts STORE correctly" {
        r command getkeys sort abc store def
    } {abc def}
    
    test "SORT_RO get keys" {
        r command getkeys sort_ro abc
    } {abc}

    test "SORT extracts multiple STORE correctly" {
        r command getkeys sort abc store invalid store stillbad store def
    } {abc def}

    test "SORT DESC" {
        assert_equal [lsort -decreasing -integer $result] [r sort tosort DESC]
    }

    test "SORT ALPHA against integer encoded strings" {
        r del mylist
        r lpush mylist 2
        r lpush mylist 1
        r lpush mylist 3
        r lpush mylist 10
        r sort mylist alpha
    } {1 10 2 3}

    test "SORT sorted set" {
        r del zset
        r zadd zset 1 a
        r zadd zset 5 b
        r zadd zset 2 c
        r zadd zset 10 d
        r zadd zset 3 e
        r sort zset alpha desc
    } {e d c b a}

    test "SORT sorted set BY nosort should retain ordering" {
        r del zset
        r zadd zset 1 a
        r zadd zset 5 b
        r zadd zset 2 c
        r zadd zset 10 d
        r zadd zset 3 e
        r multi
        r sort zset by nosort asc
        r sort zset by nosort desc
        r exec
    } {{a c e b d} {d b e c a}}

    test "SORT sorted set BY nosort + LIMIT" {
        r del zset
        r zadd zset 1 a
        r zadd zset 5 b
        r zadd zset 2 c
        r zadd zset 10 d
        r zadd zset 3 e
        assert_equal [r sort zset by nosort asc limit 0 1] {a}
        assert_equal [r sort zset by nosort desc limit 0 1] {d}
        assert_equal [r sort zset by nosort asc limit 0 2] {a c}
        assert_equal [r sort zset by nosort desc limit 0 2] {d b}
        assert_equal [r sort zset by nosort limit 5 10] {}
        assert_equal [r sort zset by nosort limit -10 100] {a c e b d}
    }

    test "SORT sorted set BY nosort works as expected from scripts" {
        r del zset
        r zadd zset 1 a
        r zadd zset 5 b
        r zadd zset 2 c
        r zadd zset 10 d
        r zadd zset 3 e
        r eval {
            return {redis.call('sort',KEYS[1],'by','nosort','asc'),
                    redis.call('sort',KEYS[1],'by','nosort','desc')}
        } 1 zset
    } {{a c e b d} {d b e c a}}

    test "SORT sorted set: +inf and -inf handling" {
        r del zset
        r zadd zset -100 a
        r zadd zset 200 b
        r zadd zset -300 c
        r zadd zset 1000000 d
        r zadd zset +inf max
        r zadd zset -inf min
        r zrange zset 0 -1
    } {min c a b d max}

    test "SORT regression for issue #19, sorting floats" {
        r flushdb
        set floats {1.1 5.10 3.10 7.44 2.1 5.75 6.12 0.25 1.15}
        foreach x $floats {
            r lpush mylist $x
        }
        assert_equal [lsort -real $floats] [r sort mylist]
    }

    test "SORT with STORE returns zero if result is empty (github issue 224)" {
        r flushdb
        r sort foo{t} store bar{t}
    } {0}

    test "SORT with STORE does not create empty lists (github issue 224)" {
        r flushdb
        r lpush foo{t} bar
        r sort foo{t} alpha limit 10 10 store zap{t}
        r exists zap{t}
    } {0}

    test "SORT with STORE removes key if result is empty (github issue 227)" {
        r flushdb
        r lpush foo{t} bar
        r sort emptylist{t} store foo{t}
        r exists foo{t}
    } {0}

    test "SORT with BY <constant> and STORE should still order output" {
        r del myset mylist
        r sadd myset a b c d e f g h i l m n o p q r s t u v z aa aaa azz
        r sort myset alpha by _ store mylist
        r lrange mylist 0 -1
    } {a aa aaa azz b c d e f g h i l m n o p q r s t u v z} {cluster:skip}

    test "SORT will complain with numerical sorting and bad doubles (1)" {
        r del myset
        r sadd myset 1 2 3 4 not-a-double
        set e {}
        catch {r sort myset} e
        set e
    } {*ERR*double*}

    test "SORT will complain with numerical sorting and bad doubles (2)" {
        r del myset
        r sadd myset 1 2 3 4
        r mset score:1 10 score:2 20 score:3 30 score:4 not-a-double
        set e {}
        catch {r sort myset by score:*} e
        set e
    } {*ERR*double*} {cluster:skip}

    test "SORT BY sub-sorts lexicographically if score is the same" {
        r del myset
        r sadd myset a b c d e f g h i l m n o p q r s t u v z aa aaa azz
        foreach ele {a aa aaa azz b c d e f g h i l m n o p q r s t u v z} {
            set score:$ele 100
        }
        r sort myset by score:*
    } {a aa aaa azz b c d e f g h i l m n o p q r s t u v z} {cluster:skip}

    test "SORT GET with pattern ending with just -> does not get hash field" {
        r del mylist
        r lpush mylist a
        r set x:a-> 100
        r sort mylist by num get x:*->
    } {100} {cluster:skip}

    test "SORT by nosort retains native order for lists" {
        r del testa
        r lpush testa 2 1 4 3 5
        r sort testa by nosort
    } {5 3 4 1 2} {cluster:skip}

    test "SORT by nosort plus store retains native order for lists" {
        r del testa
        r lpush testa 2 1 4 3 5
        r sort testa by nosort store testb
        r lrange testb 0 -1
    } {5 3 4 1 2} {cluster:skip}

    test "SORT by nosort with limit returns based on original list order" {
        r sort testa by nosort limit 0 3 store testb
        r lrange testb 0 -1
    } {5 3 4} {cluster:skip}

    test "SORT_RO - Successful case" {
        r del mylist
        r lpush mylist a
        r set x:a 100
        r sort_ro mylist by nosort get x:*->
    } {100} {cluster:skip}

    test "SORT_RO - Cannot run with STORE arg" {
        catch {r sort_ro foolist STORE bar} e
        set e
    } {ERR syntax error}

    tags {"slow"} {
        set num 100
        set res [create_random_dataset $num lpush]

        test "SORT speed, $num element list BY key, 100 times" {
            set start [clock clicks -milliseconds]
            for {set i 0} {$i < 100} {incr i} {
                set sorted [r sort tosort BY weight_* LIMIT 0 10]
            }
            set elapsed [expr [clock clicks -milliseconds]-$start]
            if {$::verbose} {
                puts -nonewline "\n  Average time to sort: [expr double($elapsed)/100] milliseconds "
                flush stdout
            }
        } {} {cluster:skip}

        test "SORT speed, $num element list BY hash field, 100 times" {
            set start [clock clicks -milliseconds]
            for {set i 0} {$i < 100} {incr i} {
                set sorted [r sort tosort BY wobj_*->weight LIMIT 0 10]
            }
            set elapsed [expr [clock clicks -milliseconds]-$start]
            if {$::verbose} {
                puts -nonewline "\n  Average time to sort: [expr double($elapsed)/100] milliseconds "
                flush stdout
            }
        } {} {cluster:skip}

        test "SORT speed, $num element list directly, 100 times" {
            set start [clock clicks -milliseconds]
            for {set i 0} {$i < 100} {incr i} {
                set sorted [r sort tosort LIMIT 0 10]
            }
            set elapsed [expr [clock clicks -milliseconds]-$start]
            if {$::verbose} {
                puts -nonewline "\n  Average time to sort: [expr double($elapsed)/100] milliseconds "
                flush stdout
            }
        }

        test "SORT speed, $num element list BY <const>, 100 times" {
            set start [clock clicks -milliseconds]
            for {set i 0} {$i < 100} {incr i} {
                set sorted [r sort tosort BY nokey LIMIT 0 10]
            }
            set elapsed [expr [clock clicks -milliseconds]-$start]
            if {$::verbose} {
                puts -nonewline "\n  Average time to sort: [expr double($elapsed)/100] milliseconds "
                flush stdout
            }
        } {} {cluster:skip}
    }

    test {SETRANGE with huge offset} {
        r lpush L 2 1 0
        # expecting a different outcome on 32 and 64 bit systems
        foreach value {9223372036854775807 2147483647} {
            catch {[r sort_ro L by a limit 2 $value]} res
            if {![string match "2" $res] && ![string match "*out of range*" $res]} {
                assert_not_equal $res "expecting an error or 2"
            }
        }
    }
}