]> git.saurik.com Git - redis.git/blame - tests/support/redis.tcl
initial basic pub/sub tests
[redis.git] / tests / support / redis.tcl
CommitLineData
f89c3a35 1# Tcl clinet library - used by test-redis.tcl script for now
2# Copyright (C) 2009 Salvatore Sanfilippo
3# Released under the BSD license like Redis itself
4#
5# Example usage:
6#
7# set r [redis 127.0.0.1 6379]
8# $r lpush mylist foo
9# $r lpush mylist bar
10# $r lrange mylist 0 -1
11# $r close
686bc0ac 12#
13# Non blocking usage example:
14#
15# proc handlePong {r type reply} {
16# puts "PONG $type '$reply'"
17# if {$reply ne "PONG"} {
18# $r ping [list handlePong]
19# }
20# }
21#
22# set r [redis]
23# $r blocking 0
24# $r get fo [list handlePong]
25#
26# vwait forever
f89c3a35 27
f1a930bb 28package require Tcl 8.5
f89c3a35 29package provide redis 0.1
30
31namespace eval redis {}
32set ::redis::id 0
33array set ::redis::fd {}
686bc0ac 34array set ::redis::blocking {}
5eedc9c6 35array set ::redis::deferred {}
686bc0ac 36array set ::redis::callback {}
37array set ::redis::state {} ;# State in non-blocking reply reading
6fcb1800 38array set ::redis::statestack {} ;# Stack of states, for nested mbulks
f89c3a35 39array set ::redis::bulkarg {}
40array set ::redis::multibulkarg {}
41
42# Flag commands requiring last argument as a bulk write operation
43foreach redis_bulk_cmd {
4589a823 44 set setnx rpush lpush rpushx lpushx linsert lset lrem sadd srem sismember echo getset smove zadd zrem zscore zincrby append zrank zrevrank hget hdel hexists setex publish
f89c3a35 45} {
46 set ::redis::bulkarg($redis_bulk_cmd) {}
47}
48
49# Flag commands requiring last argument as a bulk write operation
50foreach redis_multibulk_cmd {
1f1c7695 51 mset msetnx hset hsetnx hmset hmget
f89c3a35 52} {
53 set ::redis::multibulkarg($redis_multibulk_cmd) {}
54}
55
56unset redis_bulk_cmd
57unset redis_multibulk_cmd
58
5eedc9c6 59proc redis {{server 127.0.0.1} {port 6379} {defer 0}} {
f89c3a35 60 set fd [socket $server $port]
61 fconfigure $fd -translation binary
62 set id [incr ::redis::id]
63 set ::redis::fd($id) $fd
686bc0ac 64 set ::redis::blocking($id) 1
5eedc9c6 65 set ::redis::deferred($id) $defer
686bc0ac 66 ::redis::redis_reset_state $id
f89c3a35 67 interp alias {} ::redis::redisHandle$id {} ::redis::__dispatch__ $id
68}
69
70proc ::redis::__dispatch__ {id method args} {
71 set fd $::redis::fd($id)
686bc0ac 72 set blocking $::redis::blocking($id)
5eedc9c6 73 set deferred $::redis::deferred($id)
686bc0ac 74 if {$blocking == 0} {
75 if {[llength $args] == 0} {
76 error "Please provide a callback in non-blocking mode"
77 }
78 set callback [lindex $args end]
79 set args [lrange $args 0 end-1]
80 }
f89c3a35 81 if {[info command ::redis::__method__$method] eq {}} {
82 if {[info exists ::redis::bulkarg($method)]} {
83 set cmd "$method "
84 append cmd [join [lrange $args 0 end-1]]
85 append cmd " [string length [lindex $args end]]\r\n"
86 append cmd [lindex $args end]
87 ::redis::redis_writenl $fd $cmd
88 } elseif {[info exists ::redis::multibulkarg($method)]} {
89 set cmd "*[expr {[llength $args]+1}]\r\n"
90 append cmd "$[string length $method]\r\n$method\r\n"
91 foreach a $args {
92 append cmd "$[string length $a]\r\n$a\r\n"
93 }
94 ::redis::redis_write $fd $cmd
95 flush $fd
96 } else {
97 set cmd "$method "
98 append cmd [join $args]
99 ::redis::redis_writenl $fd $cmd
100 }
5eedc9c6
PN
101 if {!$deferred} {
102 if {$blocking} {
103 ::redis::redis_read_reply $fd
104 } else {
105 # Every well formed reply read will pop an element from this
106 # list and use it as a callback. So pipelining is supported
107 # in non blocking mode.
108 lappend ::redis::callback($id) $callback
109 fileevent $fd readable [list ::redis::redis_readable $fd $id]
110 }
686bc0ac 111 }
f89c3a35 112 } else {
113 uplevel 1 [list ::redis::__method__$method $id $fd] $args
114 }
115}
116
686bc0ac 117proc ::redis::__method__blocking {id fd val} {
118 set ::redis::blocking($id) $val
119 fconfigure $fd -blocking $val
120}
121
5eedc9c6
PN
122proc ::redis::__method__read {id fd} {
123 ::redis::redis_read_reply $fd
124}
125
f89c3a35 126proc ::redis::__method__close {id fd} {
127 catch {close $fd}
128 catch {unset ::redis::fd($id)}
686bc0ac 129 catch {unset ::redis::blocking($id)}
130 catch {unset ::redis::state($id)}
6fcb1800 131 catch {unset ::redis::statestack($id)}
686bc0ac 132 catch {unset ::redis::callback($id)}
f89c3a35 133 catch {interp alias {} ::redis::redisHandle$id {}}
134}
135
136proc ::redis::__method__channel {id fd} {
137 return $fd
138}
139
140proc ::redis::redis_write {fd buf} {
141 puts -nonewline $fd $buf
142}
143
144proc ::redis::redis_writenl {fd buf} {
145 redis_write $fd $buf
146 redis_write $fd "\r\n"
147 flush $fd
148}
149
150proc ::redis::redis_readnl {fd len} {
151 set buf [read $fd $len]
152 read $fd 2 ; # discard CR LF
153 return $buf
154}
155
156proc ::redis::redis_bulk_read {fd} {
157 set count [redis_read_line $fd]
158 if {$count == -1} return {}
159 set buf [redis_readnl $fd $count]
160 return $buf
161}
162
163proc ::redis::redis_multi_bulk_read fd {
164 set count [redis_read_line $fd]
165 if {$count == -1} return {}
166 set l {}
167 for {set i 0} {$i < $count} {incr i} {
168 lappend l [redis_read_reply $fd]
169 }
170 return $l
171}
172
173proc ::redis::redis_read_line fd {
174 string trim [gets $fd]
175}
176
177proc ::redis::redis_read_reply fd {
178 set type [read $fd 1]
179 switch -exact -- $type {
180 : -
181 + {redis_read_line $fd}
182 - {return -code error [redis_read_line $fd]}
183 $ {redis_bulk_read $fd}
184 * {redis_multi_bulk_read $fd}
185 default {return -code error "Bad protocol, $type as reply type byte"}
186 }
187}
686bc0ac 188
189proc ::redis::redis_reset_state id {
190 set ::redis::state($id) [dict create buf {} mbulk -1 bulk -1 reply {}]
6fcb1800 191 set ::redis::statestack($id) {}
686bc0ac 192}
193
194proc ::redis::redis_call_callback {id type reply} {
195 set cb [lindex $::redis::callback($id) 0]
196 set ::redis::callback($id) [lrange $::redis::callback($id) 1 end]
197 uplevel #0 $cb [list ::redis::redisHandle$id $type $reply]
198 ::redis::redis_reset_state $id
199}
200
201# Read a reply in non-blocking mode.
202proc ::redis::redis_readable {fd id} {
203 if {[eof $fd]} {
204 redis_call_callback $id eof {}
205 ::redis::__method__close $id $fd
206 return
207 }
208 if {[dict get $::redis::state($id) bulk] == -1} {
209 set line [gets $fd]
210 if {$line eq {}} return ;# No complete line available, return
211 switch -exact -- [string index $line 0] {
212 : -
213 + {redis_call_callback $id reply [string range $line 1 end-1]}
214 - {redis_call_callback $id err [string range $line 1 end-1]}
215 $ {
216 dict set ::redis::state($id) bulk \
217 [expr [string range $line 1 end-1]+2]
218 if {[dict get $::redis::state($id) bulk] == 1} {
219 # We got a $-1, hack the state to play well with this.
220 dict set ::redis::state($id) bulk 2
221 dict set ::redis::state($id) buf "\r\n"
222 ::redis::redis_readable $fd $id
223 }
224 }
6fcb1800 225 * {
226 dict set ::redis::state($id) mbulk [string range $line 1 end-1]
227 # Handle *-1
228 if {[dict get $::redis::state($id) mbulk] == -1} {
229 redis_call_callback $id reply {}
230 }
231 }
686bc0ac 232 default {
233 redis_call_callback $id err \
234 "Bad protocol, $type as reply type byte"
235 }
236 }
237 } else {
238 set totlen [dict get $::redis::state($id) bulk]
239 set buflen [string length [dict get $::redis::state($id) buf]]
240 set toread [expr {$totlen-$buflen}]
241 set data [read $fd $toread]
242 set nread [string length $data]
243 dict append ::redis::state($id) buf $data
244 # Check if we read a complete bulk reply
245 if {[string length [dict get $::redis::state($id) buf]] ==
246 [dict get $::redis::state($id) bulk]} {
247 if {[dict get $::redis::state($id) mbulk] == -1} {
248 redis_call_callback $id reply \
249 [string range [dict get $::redis::state($id) buf] 0 end-2]
250 } else {
251 dict with ::redis::state($id) {
252 lappend reply [string range $buf 0 end-2]
253 incr mbulk -1
254 set bulk -1
255 }
256 if {[dict get $::redis::state($id) mbulk] == 0} {
257 redis_call_callback $id reply \
258 [dict get $::redis::state($id) reply]
259 }
260 }
261 }
262 }
263}