Skip to main content

Redis Parser

Overview

Redis Parser validates Redis commands WITHOUT requiring a real Redis server. It parses command syntax and validates argument counts.

Specifications

PropertyValue
Language IDredis-parser
Base OSAlpine Linux 3.20
RuntimeNode.js 22
Rootfs Size300 MB
Memory256 MB
File Extension.redis
Execution Time~2.7s

Supported Commands

CategoryCommands
StringsSET, GET, MSET, MGET, INCR, DECR, APPEND, STRLEN
HashesHSET, HGET, HMSET, HMGET, HGETALL, HDEL, HEXISTS
ListsLPUSH, RPUSH, LPOP, RPOP, LRANGE, LLEN, LINDEX
SetsSADD, SREM, SMEMBERS, SISMEMBER, SINTER, SUNION
Sorted SetsZADD, ZREM, ZRANGE, ZRANK, ZSCORE, ZCOUNT
KeysDEL, EXISTS, EXPIRE, TTL, KEYS, SCAN, TYPE
TransactionsMULTI, EXEC, DISCARD, WATCH
ServerPING, INFO, DBSIZE, FLUSHDB

Infrastructure

Step 1: Create Docker Directory

Create working directory for Redis parser
mkdir -p /tmp/docker/redis-parser
cd /tmp/docker/redis-parser

Step 2: Create redis-run.js Script

Create Node.js parser wrapper script
cat > redis-run.js << 'EOF'
#!/usr/bin/env node
const fs = require('fs');

const REDIS_COMMANDS = {
// String commands
'SET': { min: 2, max: -1, desc: 'SET key value [EX|PX|EXAT|PXAT time] [NX|XX] [KEEPTTL] [GET]' },
'GET': { min: 1, max: 1, desc: 'GET key' },
'MSET': { min: 2, max: -1, desc: 'MSET key value [key value ...]' },
'MGET': { min: 1, max: -1, desc: 'MGET key [key ...]' },
'INCR': { min: 1, max: 1, desc: 'INCR key' },
'INCRBY': { min: 2, max: 2, desc: 'INCRBY key increment' },
'DECR': { min: 1, max: 1, desc: 'DECR key' },
'DECRBY': { min: 2, max: 2, desc: 'DECRBY key decrement' },
'APPEND': { min: 2, max: 2, desc: 'APPEND key value' },
'STRLEN': { min: 1, max: 1, desc: 'STRLEN key' },
'GETRANGE': { min: 3, max: 3, desc: 'GETRANGE key start end' },
'SETRANGE': { min: 3, max: 3, desc: 'SETRANGE key offset value' },
'SETNX': { min: 2, max: 2, desc: 'SETNX key value' },
'SETEX': { min: 3, max: 3, desc: 'SETEX key seconds value' },
'GETSET': { min: 2, max: 2, desc: 'GETSET key value' },
'GETDEL': { min: 1, max: 1, desc: 'GETDEL key' },

// Key commands
'DEL': { min: 1, max: -1, desc: 'DEL key [key ...]' },
'EXISTS': { min: 1, max: -1, desc: 'EXISTS key [key ...]' },
'EXPIRE': { min: 2, max: 3, desc: 'EXPIRE key seconds [NX|XX|GT|LT]' },
'EXPIREAT': { min: 2, max: 3, desc: 'EXPIREAT key unix-time-seconds [NX|XX|GT|LT]' },
'TTL': { min: 1, max: 1, desc: 'TTL key' },
'PTTL': { min: 1, max: 1, desc: 'PTTL key' },
'PERSIST': { min: 1, max: 1, desc: 'PERSIST key' },
'KEYS': { min: 1, max: 1, desc: 'KEYS pattern' },
'SCAN': { min: 1, max: -1, desc: 'SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]' },
'TYPE': { min: 1, max: 1, desc: 'TYPE key' },
'RENAME': { min: 2, max: 2, desc: 'RENAME key newkey' },
'RENAMENX': { min: 2, max: 2, desc: 'RENAMENX key newkey' },
'UNLINK': { min: 1, max: -1, desc: 'UNLINK key [key ...]' },

// Hash commands
'HSET': { min: 3, max: -1, desc: 'HSET key field value [field value ...]' },
'HGET': { min: 2, max: 2, desc: 'HGET key field' },
'HMSET': { min: 3, max: -1, desc: 'HMSET key field value [field value ...]' },
'HMGET': { min: 2, max: -1, desc: 'HMGET key field [field ...]' },
'HGETALL': { min: 1, max: 1, desc: 'HGETALL key' },
'HDEL': { min: 2, max: -1, desc: 'HDEL key field [field ...]' },
'HEXISTS': { min: 2, max: 2, desc: 'HEXISTS key field' },
'HINCRBY': { min: 3, max: 3, desc: 'HINCRBY key field increment' },
'HKEYS': { min: 1, max: 1, desc: 'HKEYS key' },
'HVALS': { min: 1, max: 1, desc: 'HVALS key' },
'HLEN': { min: 1, max: 1, desc: 'HLEN key' },
'HSETNX': { min: 3, max: 3, desc: 'HSETNX key field value' },
'HSCAN': { min: 2, max: -1, desc: 'HSCAN key cursor [MATCH pattern] [COUNT count]' },

// List commands
'LPUSH': { min: 2, max: -1, desc: 'LPUSH key element [element ...]' },
'RPUSH': { min: 2, max: -1, desc: 'RPUSH key element [element ...]' },
'LPOP': { min: 1, max: 2, desc: 'LPOP key [count]' },
'RPOP': { min: 1, max: 2, desc: 'RPOP key [count]' },
'LRANGE': { min: 3, max: 3, desc: 'LRANGE key start stop' },
'LINDEX': { min: 2, max: 2, desc: 'LINDEX key index' },
'LLEN': { min: 1, max: 1, desc: 'LLEN key' },
'LSET': { min: 3, max: 3, desc: 'LSET key index element' },
'LINSERT': { min: 4, max: 4, desc: 'LINSERT key BEFORE|AFTER pivot element' },
'LREM': { min: 3, max: 3, desc: 'LREM key count element' },
'LTRIM': { min: 3, max: 3, desc: 'LTRIM key start stop' },
'BLPOP': { min: 2, max: -1, desc: 'BLPOP key [key ...] timeout' },
'BRPOP': { min: 2, max: -1, desc: 'BRPOP key [key ...] timeout' },

// Set commands
'SADD': { min: 2, max: -1, desc: 'SADD key member [member ...]' },
'SREM': { min: 2, max: -1, desc: 'SREM key member [member ...]' },
'SMEMBERS': { min: 1, max: 1, desc: 'SMEMBERS key' },
'SISMEMBER': { min: 2, max: 2, desc: 'SISMEMBER key member' },
'SCARD': { min: 1, max: 1, desc: 'SCARD key' },
'SPOP': { min: 1, max: 2, desc: 'SPOP key [count]' },
'SRANDMEMBER': { min: 1, max: 2, desc: 'SRANDMEMBER key [count]' },
'SUNION': { min: 1, max: -1, desc: 'SUNION key [key ...]' },
'SINTER': { min: 1, max: -1, desc: 'SINTER key [key ...]' },
'SDIFF': { min: 1, max: -1, desc: 'SDIFF key [key ...]' },
'SUNIONSTORE': { min: 2, max: -1, desc: 'SUNIONSTORE destination key [key ...]' },
'SINTERSTORE': { min: 2, max: -1, desc: 'SINTERSTORE destination key [key ...]' },
'SDIFFSTORE': { min: 2, max: -1, desc: 'SDIFFSTORE destination key [key ...]' },
'SMOVE': { min: 3, max: 3, desc: 'SMOVE source destination member' },
'SSCAN': { min: 2, max: -1, desc: 'SSCAN key cursor [MATCH pattern] [COUNT count]' },

// Sorted Set commands
'ZADD': { min: 3, max: -1, desc: 'ZADD key [NX|XX] [GT|LT] [CH] [INCR] score member [score member ...]' },
'ZREM': { min: 2, max: -1, desc: 'ZREM key member [member ...]' },
'ZSCORE': { min: 2, max: 2, desc: 'ZSCORE key member' },
'ZRANK': { min: 2, max: 2, desc: 'ZRANK key member' },
'ZREVRANK': { min: 2, max: 2, desc: 'ZREVRANK key member' },
'ZRANGE': { min: 3, max: -1, desc: 'ZRANGE key min max [BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES]' },
'ZREVRANGE': { min: 3, max: -1, desc: 'ZREVRANGE key start stop [WITHSCORES]' },
'ZRANGEBYSCORE': { min: 3, max: -1, desc: 'ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]' },
'ZCARD': { min: 1, max: 1, desc: 'ZCARD key' },
'ZCOUNT': { min: 3, max: 3, desc: 'ZCOUNT key min max' },
'ZINCRBY': { min: 3, max: 3, desc: 'ZINCRBY key increment member' },
'ZSCAN': { min: 2, max: -1, desc: 'ZSCAN key cursor [MATCH pattern] [COUNT count]' },
'ZPOPMIN': { min: 1, max: 2, desc: 'ZPOPMIN key [count]' },
'ZPOPMAX': { min: 1, max: 2, desc: 'ZPOPMAX key [count]' },

// Pub/Sub
'PUBLISH': { min: 2, max: 2, desc: 'PUBLISH channel message' },
'SUBSCRIBE': { min: 1, max: -1, desc: 'SUBSCRIBE channel [channel ...]' },
'UNSUBSCRIBE': { min: 0, max: -1, desc: 'UNSUBSCRIBE [channel [channel ...]]' },
'PSUBSCRIBE': { min: 1, max: -1, desc: 'PSUBSCRIBE pattern [pattern ...]' },
'PUNSUBSCRIBE': { min: 0, max: -1, desc: 'PUNSUBSCRIBE [pattern [pattern ...]]' },

// Transaction
'MULTI': { min: 0, max: 0, desc: 'MULTI' },
'EXEC': { min: 0, max: 0, desc: 'EXEC' },
'DISCARD': { min: 0, max: 0, desc: 'DISCARD' },
'WATCH': { min: 1, max: -1, desc: 'WATCH key [key ...]' },
'UNWATCH': { min: 0, max: 0, desc: 'UNWATCH' },

// Server
'PING': { min: 0, max: 1, desc: 'PING [message]' },
'ECHO': { min: 1, max: 1, desc: 'ECHO message' },
'INFO': { min: 0, max: 1, desc: 'INFO [section]' },
'DBSIZE': { min: 0, max: 0, desc: 'DBSIZE' },
'FLUSHDB': { min: 0, max: 1, desc: 'FLUSHDB [ASYNC|SYNC]' },
'FLUSHALL': { min: 0, max: 1, desc: 'FLUSHALL [ASYNC|SYNC]' },
'SELECT': { min: 1, max: 1, desc: 'SELECT index' },
'SAVE': { min: 0, max: 0, desc: 'SAVE' },
'BGSAVE': { min: 0, max: 1, desc: 'BGSAVE [SCHEDULE]' },
'TIME': { min: 0, max: 0, desc: 'TIME' },
'CONFIG': { min: 1, max: -1, desc: 'CONFIG GET|SET|RESETSTAT|REWRITE ...' },
'CLIENT': { min: 1, max: -1, desc: 'CLIENT ID|KILL|LIST|GETNAME|SETNAME|PAUSE ...' },
};

function parseCommand(line) {
const tokens = [];
let current = '';
let inQuote = false;
let quoteChar = null;

for (let i = 0; i < line.length; i++) {
const char = line[i];

if (!inQuote && (char === '"' || char === "'")) {
inQuote = true;
quoteChar = char;
} else if (inQuote && char === quoteChar) {
inQuote = false;
quoteChar = null;
} else if (!inQuote && char === ' ') {
if (current) {
tokens.push(current);
current = '';
}
} else {
current += char;
}
}

if (current) {
tokens.push(current);
}

return tokens;
}

function validateCommand(line, lineNumber) {
const result = {
line: lineNumber,
command: null,
args: [],
valid: true,
error: null,
description: null
};

const tokens = parseCommand(line);
if (tokens.length === 0) {
return null;
}

const cmd = tokens[0].toUpperCase();
const args = tokens.slice(1);

result.command = cmd;
result.args = args;

const cmdSpec = REDIS_COMMANDS[cmd];
if (!cmdSpec) {
result.valid = false;
result.error = `Unknown command: ${cmd}`;
return result;
}

result.description = cmdSpec.desc;

if (args.length < cmdSpec.min) {
result.valid = false;
result.error = `Too few arguments. Expected at least ${cmdSpec.min}, got ${args.length}`;
return result;
}

if (cmdSpec.max !== -1 && args.length > cmdSpec.max) {
result.valid = false;
result.error = `Too many arguments. Expected at most ${cmdSpec.max}, got ${args.length}`;
return result;
}

return result;
}

function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.error('Usage: redis-run.js <file.redis>');
process.exit(1);
}

const commandFile = args[0];
let content;
try {
content = fs.readFileSync(commandFile, 'utf8');
} catch (err) {
console.error(`Error reading file: ${err.message}`);
process.exit(1);
}

const lines = content.split('\n');
const results = [];
let hasErrors = false;

lines.forEach((line, i) => {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) {
return;
}

const result = validateCommand(trimmed, i + 1);
if (result) {
results.push(result);
if (!result.valid) {
hasErrors = true;
}
}
});

console.log('=== Redis Commands Validation ===');
console.log(`Status: ${hasErrors ? 'INVALID' : 'VALID'}`);
console.log(`Total commands: ${results.length}`);
console.log('');

results.forEach((r) => {
const status = r.valid ? 'OK' : 'ERROR';
console.log(`Line ${r.line}: [${status}] ${r.command} ${r.args.join(' ')}`);

if (r.error) {
console.log(` Error: ${r.error}`);
}

if (r.description && r.valid) {
console.log(` Syntax: ${r.description}`);
}
});

console.log('');
console.log('=== Summary ===');
const valid = results.filter(r => r.valid).length;
const invalid = results.filter(r => !r.valid).length;
console.log(`Valid: ${valid}/${results.length}`);
console.log(`Invalid: ${invalid}/${results.length}`);

process.exit(hasErrors ? 1 : 0);
}

main();
EOF

Step 3: Create Dockerfile

Create Dockerfile with Node.js and parser dependencies
cat > Dockerfile << 'EOF'
FROM node:22-alpine3.20

WORKDIR /opt/redis-parser

COPY redis-run.js /usr/local/bin/redis-run
RUN chmod +x /usr/local/bin/redis-run

CMD ["node", "--version"]
EOF

Step 4: Build and Push Docker Image

Build and push Docker image to ttl.sh registry
docker build -t ttl.sh/llm-fc-redis-parser:24h .
docker push ttl.sh/llm-fc-redis-parser:24h

Step 5: Create Rootfs

Create rootfs from Docker image
sudo infra.operator rootfs from-docker \
--name redis-parser \
--image ttl.sh/llm-fc-redis-parser:24h \
--size 300

Step 6: Create Snapshot

Create VM snapshot for fast boot
sudo infra.operator snapshot create \
--lang redis-parser \
--mem 256 \
--vcpus 1

Step 7: Upload to S3

Upload rootfs and snapshot to S3 bucket
export AWS_ACCESS_KEY_ID="your-key"
export AWS_SECRET_ACCESS_KEY="your-secret"
export AWS_DEFAULT_REGION="us-east-1"

sudo -E infra.operator rootfs upload --lang redis-parser --bucket runner-codes
sudo -E infra.operator snapshot upload --lang redis-parser --bucket runner-codes

Step 8: Test Execution

Test parser with sample SQL
sudo infra.operator host \
--lang redis-parser \
--snapshot \
--mem 256 \
--vcpus 1 \
--code "SET user:1 john
GET user:1
HSET user:1:profile name John age 30
HGETALL user:1:profile"

Examples

Basic Commands

Request
{
"trace_id": "redis-basic-001",
"lang": "redis-parser",
"code": "SET user:1 john\nGET user:1\nHSET user:1:profile name John age 30",
"timeout": 30
}
Response
{
"trace_id": "redis-basic-001",
"stdout": "=== Redis Commands Validation ===\nStatus: VALID\nTotal commands: 3\n\nLine 1: [OK] SET user:1 john\n Syntax: SET key value [EX|PX|EXAT|PXAT time] [NX|XX] [KEEPTTL] [GET]\nLine 2: [OK] GET user:1\n Syntax: GET key\nLine 3: [OK] HSET user:1:profile name John age 30\n Syntax: HSET key field value [field value ...]\n\n=== Summary ===\nValid: 3/3\nInvalid: 0/3\n",
"stderr": "",
"exit_code": 0
}

List Operations

Request
{
"trace_id": "redis-list-001",
"lang": "redis-parser",
"code": "LPUSH mylist item1 item2 item3\nRPUSH mylist item4\nLRANGE mylist 0 -1\nLLEN mylist",
"timeout": 30
}
Response
{
"trace_id": "redis-list-001",
"stdout": "=== Redis Commands Validation ===\nStatus: VALID\nTotal commands: 4\n\nLine 1: [OK] LPUSH mylist item1 item2 item3\n Syntax: LPUSH key element [element ...]\nLine 2: [OK] RPUSH mylist item4\n Syntax: RPUSH key element [element ...]\nLine 3: [OK] LRANGE mylist 0 -1\n Syntax: LRANGE key start stop\nLine 4: [OK] LLEN mylist\n Syntax: LLEN key\n\n=== Summary ===\nValid: 4/4\nInvalid: 0/4\n",
"stderr": "",
"exit_code": 0
}

Invalid Commands Detection

Request
{
"trace_id": "redis-invalid-001",
"lang": "redis-parser",
"code": "SET key\nGET\nUNKNOWNCMD arg1 arg2",
"timeout": 30
}
Response
{
"trace_id": "redis-invalid-001",
"stdout": "=== Redis Commands Validation ===\nStatus: INVALID\nTotal commands: 3\n\nLine 1: [ERROR] SET key\n Error: Too few arguments. Expected at least 2, got 1\nLine 2: [ERROR] GET\n Error: Too few arguments. Expected at least 1, got 0\nLine 3: [ERROR] UNKNOWNCMD arg1 arg2\n Error: Unknown command: UNKNOWNCMD\n\n=== Summary ===\nValid: 0/3\nInvalid: 3/3\n",
"stderr": "",
"exit_code": 1
}

Executor Configuration

Go code
// pkg/guest/executor.go
"redis-parser": {
Name: "redis-parser",
Extension: ".redis",
Command: "/usr/local/bin/node",
Args: []string{"/usr/local/bin/redis-run"},
},