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

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

E-mail: