Annotation of parser3/src/classes/redis.C, revision 1.3

1.1       moko        1: /** @file
                      2:        Parser: @b redis parser class.
                      3: 
                      4:        Copyright (c) 2001-2026 Art. Lebedev Studio (https://www.artlebedev.com)
                      5:        Authors: Konstantin Morshnev <moko@design.ru>
                      6: */
                      7: 
1.3     ! moko        8: #include "classes.h"
        !             9: #include "pa_vmethod_frame.h"
        !            10: 
1.2       moko       11: #include "pa_vredis.h"
                     12: #include "pa_request.h"
1.1       moko       13: #include "pa_varray.h"
                     14: #include "pa_vbool.h"
                     15: #include "pa_vfile.h"
                     16: 
1.3     ! moko       17: volatile const char * IDENT_REDIS_C="$Id: redis.C,v 1.2 2026/10/06 23:54:23 moko Exp $" IDENT_PA_VREDIS_H;
1.1       moko       18: 
                     19: // defines
                     20: 
                     21: #define DEFAULT_TIMEOUT 2 // seconds
                     22: 
                     23: class MRedis: public Methoded {
                     24: public: // VStateless_class
                     25:        Value* create_new_value(Pool&) { return new VRedis(); }
                     26: public:
                     27:        MRedis();
                     28: };
                     29: 
                     30: DECLARE_CLASS_VAR(redis, new MRedis);
                     31: 
                     32: #ifdef WITH_REDIS
                     33: 
                     34: static void _open(Request& r, MethodParams& params) {
                     35:        VRedis& self=GET_SELF(r, VRedis);
                     36: 
                     37:        const char* host="localhost";
                     38:        int port=6379;
                     39:        bool tcp_specified=false;
                     40:        const char* unix_socket=0;
                     41:        double timeout=DEFAULT_TIMEOUT;
                     42:        const char* user=0;
                     43:        const char* password=0;
                     44:        int db=0;
                     45:        int protocol=3;
                     46:        int auto_reconnect=0;
                     47: 
                     48:        if(params.count()>0)
                     49:                if(HashStringValue* options=params.as_hash(0)) {
                     50:                        for(HashStringValue::Iterator i(*options); i; i.next()) {
                     51:                                String::Body key=i.key();
                     52:                                Value* value=i.value();
                     53:                                if(key=="host") {
                     54:                                        host=value->as_string().cstr();
                     55:                                        tcp_specified=true;
                     56:                                } else if(key=="port") {
                     57:                                        port=r.process(*value).as_int();
                     58:                                        tcp_specified=true;
                     59:                                } else if(key=="unix_socket") {
                     60:                                        unix_socket=value->as_string().cstr();
                     61:                                } else if(key=="timeout") {
                     62:                                        timeout=r.process(*value).as_double();
                     63:                                } else if(key=="user") {
                     64:                                        user=value->as_string().cstr();
                     65:                                } else if(key=="password") {
                     66:                                        password=value->as_string().cstr();
                     67:                                } else if(key=="db") {
                     68:                                        db=r.process(*value).as_int();
                     69:                                } else if(key=="protocol") {
                     70:                                        protocol=r.process(*value).as_int();
                     71:                                        if(protocol!=2 && protocol!=3)
                     72:                                                throw Exception(PARSER_RUNTIME, 0, "protocol must be 2 or 3");
                     73:                                } else if(key=="auto_reconnect") {
                     74:                                        auto_reconnect=r.process(*value).as_int();
                     75:                                } else
                     76:                                        throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
                     77:                        }
                     78:                }
                     79: 
                     80:        if(unix_socket && tcp_specified)
                     81:                throw Exception(PARSER_RUNTIME, 0, "unix_socket can not be used with host or port");
                     82: 
                     83:        redisOptions options;
                     84:        memset(&options, 0, sizeof(options));
                     85:        if(unix_socket)
                     86:                REDIS_OPTIONS_SET_UNIX(&options, unix_socket);
                     87:        else
                     88:                REDIS_OPTIONS_SET_TCP(&options, host, port);
                     89: 
                     90:        struct timeval tv;
                     91:        if(timeout>0) { // 0 is none
                     92:                tv.tv_sec=(long)timeout;
                     93:                tv.tv_usec=(long)((timeout-tv.tv_sec)*1000000);
                     94:                options.connect_timeout=&tv;
                     95:                options.command_timeout=&tv;
                     96:        }
                     97: 
                     98:        // ^r.open[] of an opened object: close the old connection first
                     99:        if(self.fcontext) {
                    100:                redisFree(self.fcontext);
                    101:                self.fcontext=0;
                    102:        }
                    103: 
                    104:        redisContext* context=redisConnectWithOptions(&options);
                    105:        if(!context)
                    106:                throw Exception("redis", 0, "connect failed: out of memory");
                    107:        if(context->err) {
                    108:                const char* error=pa_strdup(context->errstr);
                    109:                redisFree(context);
                    110:                throw Exception("redis", 0, "connect failed: %s", error);
                    111:        }
                    112: 
                    113:        self.fcontext=context;
                    114:        self.fuser=user;
                    115:        self.fpassword=password;
                    116:        self.fdb=db;
                    117:        self.fprotocol=protocol;
                    118:        self.fauto_reconnect=auto_reconnect;
                    119: 
                    120:        try {
                    121:                self.handshake();
                    122:        } catch(...) {
                    123:                redisFree(context);
                    124:                self.fcontext=0;
                    125:                throw;
                    126:        }
                    127: }
                    128: 
                    129: static void _release(Request& r, MethodParams&) {
                    130:        VRedis& self=GET_SELF(r, VRedis);
                    131: 
                    132:        if(self.fcontext) {
                    133:                redisFree(self.fcontext);
                    134:                self.fcontext=0;
                    135:        }
                    136: }
                    137: 
                    138: static Value* reply_value(redisReply& reply, bool binary, const String& command) {
                    139:        switch(reply.type) {
                    140:                case REDIS_REPLY_STRING:
                    141:                case REDIS_REPLY_STATUS:
                    142:                case REDIS_REPLY_VERB:
                    143:                case REDIS_REPLY_BIGNUM: {
                    144:                        char* data=pa_strdup(reply.str, reply.len);
                    145:                        if(binary) {
                    146:                                VFile& file=*new VFile;
                    147:                                file.set_binary(true/*tainted*/, data, reply.len);
                    148:                                return &file;
                    149:                        }
                    150:                        return new VString(*new String(String::C(data, reply.len), String::L_TAINTED));
                    151:                }
                    152:                case REDIS_REPLY_INTEGER:
                    153:                        if(reply.integer>=PA_WINT_MIN && reply.integer<=PA_WINT_MAX)
                    154:                                return new VInt(reply.integer);
                    155:                        // beyond the precise int range
                    156:                        return new VString(*new String(pa_itoa(reply.integer)));
                    157:                case REDIS_REPLY_DOUBLE:
                    158:                        return new VDouble(reply.dval);
                    159:                case REDIS_REPLY_BOOL:
                    160:                        return &VBool::get(reply.integer!=0);
                    161:                case REDIS_REPLY_NIL:
                    162:                        return VVoid::get();
                    163:                case REDIS_REPLY_ARRAY:
                    164:                case REDIS_REPLY_SET:
                    165:                case REDIS_REPLY_PUSH: {
                    166:                        VArray& result=*new VArray(reply.elements);
                    167:                        ArrayValue& array=result.array();
                    168:                        for(size_t i=0; i<reply.elements; i++)
                    169:                                array+=reply_value(*reply.element[i], binary, command);
                    170:                        return &result;
                    171:                }
                    172:                case REDIS_REPLY_MAP: {
                    173:                        VHash& result=*new VHash;
                    174:                        for(size_t i=0; i+1<reply.elements; i+=2)
                    175:                                result.hash().put(reply_value(*reply.element[i], false, command)->as_string(), reply_value(*reply.element[i+1], binary, command));
                    176:                        return &result;
                    177:                }
                    178:                case REDIS_REPLY_ERROR:
                    179:                        throw Exception("redis", &command, "%s", reply.str);
                    180:                case REDIS_REPLY_ATTR:
                    181:                        return VVoid::get();
                    182:        }
                    183:        throw Exception("redis", &command, "unknown reply type %d", reply.type);
                    184: }
                    185: 
                    186: // a string, a number, a bool or a file
                    187: static void add_argument(Array<const char*>& argv, Array<size_t>& lengths, Value& value) {
                    188:        if(VFile* file=dynamic_cast<VFile*>(&value)) {
                    189:                argv+=file->value_ptr();
                    190:                lengths+=file->value_size();
                    191:        } else if(dynamic_cast<VBool*>(&value)) {
                    192:                // as redis itself encodes booleans in integer replies
                    193:                argv+=value.as_bool() ? "1" : "0";
                    194:                lengths+=1;
                    195:        } else if(dynamic_cast<VArray*>(&value) || dynamic_cast<VHash*>(&value)) {
                    196:                throw Exception(PARSER_RUNTIME, 0, "redis command argument can not be a nested array or a hash");
                    197:        } else {
                    198:                const char* string=value.as_string().cstr();
                    199:                argv+=string;
                    200:                lengths+=strlen(string);
                    201:        }
                    202: }
                    203: 
                    204: // arguments start at params[first]: strings, numbers, files, not nested arrays and hashes as key value pairs
                    205: static void redis_command(Request& r, MethodParams& params, const String& command, size_t first, bool binary) {
                    206:        VRedis& self=GET_SELF(r, VRedis);
                    207: 
                    208:        Array<const char*> argv;
                    209:        Array<size_t> lengths;
                    210:        const char* command_cstr=command.cstr();
                    211:        argv+=command_cstr;
                    212:        lengths+=strlen(command_cstr);
                    213: 
                    214:        for(size_t i=first; i<params.count(); i++) {
                    215:                Value& param=params[i];
                    216:                if(VArray* array=dynamic_cast<VArray*>(&param)) {
                    217:                        for(ArrayValue::Iterator e(array->array()); e; e.next())
                    218:                                if(e.value())
                    219:                                        add_argument(argv, lengths, *e.value());
                    220:                } else if(VHash* hash=dynamic_cast<VHash*>(&param)) {
                    221:                        for(HashStringValue::Iterator e(hash->hash()); e; e.next()) {
                    222:                                const char* key=e.key().cstr();
                    223:                                argv+=key;
                    224:                                lengths+=strlen(key);
                    225:                                add_argument(argv, lengths, *e.value());
                    226:                        }
                    227:                } else
                    228:                        add_argument(argv, lengths, param);
                    229:        }
                    230: 
                    231:        redisReply* reply=self.command((int)argv.count(), argv.ptr(0), lengths.ptr(0));
                    232:        Temp_redis_reply temp_reply(reply);
                    233: 
                    234:        r.write(*reply_value(*reply, binary, command));
                    235: }
                    236: 
                    237: // ^r.call[command;arguments...]
                    238: static void _call(Request& r, MethodParams& params) {
                    239:        redis_command(r, params, params.as_string(0, "command must be string"), 1, false);
                    240: }
                    241: 
                    242: // ^r.call_binary[command;arguments...]: the strings of the reply are files
                    243: static void _call_binary(Request& r, MethodParams& params) {
                    244:        redis_command(r, params, params.as_string(0, "command must be string"), 1, true);
                    245: }
                    246: 
                    247: // ^r.command_name[arguments...]: the command is the name of the method called, see VRedis::get_element4call
                    248: void redis_command_method(Request& r, MethodParams& params) {
                    249:        redis_command(r, params, *r.get_method_frame()->method.name, 0, false);
                    250: }
                    251: 
                    252: #else
                    253: 
                    254: static void _open(Request&, MethodParams&) {
                    255:        throw Exception("redis", 0, "compiled without redis support");
                    256: }
                    257: 
                    258: #endif // WITH_REDIS
                    259: 
                    260: MRedis::MRedis(): Methoded("redis") {
                    261:        // ^redis::open[ $.host[localhost] $.port(6379) $.unix_socket[path] $.user[] $.password[] $.db(0) $.timeout(seconds, 0 is none) $.protocol(3) $.auto_reconnect(seconds) ]
                    262:        add_native_method("open", Method::CT_DYNAMIC, _open, 0, 1);
                    263: 
                    264: #ifdef WITH_REDIS
                    265:        // ^r.call[command;arguments...]
                    266:        add_native_method("call", Method::CT_DYNAMIC, _call, 1, MAX_REDIS_COMMAND_PARAMS);
                    267: 
                    268:        // ^r.call_binary[command;arguments...]
                    269:        add_native_method("call_binary", Method::CT_DYNAMIC, _call_binary, 1, MAX_REDIS_COMMAND_PARAMS);
                    270: 
                    271:        // ^r.release[]
                    272:        add_native_method("release", Method::CT_DYNAMIC, _release, 0, 0);
                    273: #endif // WITH_REDIS
                    274: }

E-mail: