Skip to content

Instantly share code, notes, and snippets.

@jgaskins
Created May 28, 2020 14:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jgaskins/528cbaf494ccadcf3611be0bf00b50e6 to your computer and use it in GitHub Desktop.
Save jgaskins/528cbaf494ccadcf3611be0bf00b50e6 to your computer and use it in GitHub Desktop.
Simple Redis implementation in Crystal
require "socket"
module Redis
alias Type = String | Int64 | Nil | Array(Type)
class Future
property! value : Type
end
module Commands
abstract def get(key)
abstract def keys(pattern)
abstract def incr(key)
abstract def del(key)
end
class Connection
include Commands
def initialize(@socket : TCPSocket)
end
def get(key)
command("get #{key}").as(String)
end
def keys(pattern)
command("keys #{pattern}").as(Array).map(&.as(String))
end
def incr(key)
command("incr #{key}").as(Int64)
end
def del(key)
command("del #{key}").as(Int64)
end
def pipelined
pipeline = Pipeline.new(self)
yield pipeline
pipeline.commit
end
def command(string) : Type
queue string
@socket.flush
Parser.new(@socket).receive
end
def queue(string)
@socket << string << "\r\n"
end
end
class Pipeline
include Commands
def initialize(@connection : Connection)
@futures = Array(Future).new
end
def get(key)
command "get #{key}"
end
def keys(pattern)
command "keys #{pattern}"
end
def incr(key)
command "incr #{key}"
end
def del(key)
command "del #{key}"
end
def command(string)
future = Future.new
@connection.queue string
@futures << future
future
end
def commit : Array(Future)
parser = Parser.new(@connection.@socket)
@futures.each do |future|
future.value = parser.receive
end
@futures
end
end
struct Parser
def initialize(@io : IO)
end
def receive
case byte = @io.read_byte
when '*'
length = @io.gets.not_nil!.to_i
Array(Type).new(length) { receive }
when '$'
length = @io.gets.not_nil!.to_i
if length >= 0
@io.read_string(length).tap { @io.gets }
end
when ':'
@io.gets.not_nil!.to_i64
end
end
end
end
redis = Redis::Connection.new(TCPSocket.new("localhost", 6379))
redis.get "foo"
redis.keys "*"
redis.incr "counter"
redis.del "omg"
incr, get = redis.pipelined do |redis|
redis.incr "counter"
redis.get "foo"
end
redis.del "counter"
incr.value
get.value
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment