static void sendStaticFileReply(cli *c) { robj *o; if ((o = lookupKeyRead(c->db, c->http.file)) == NULL) SEND_404 else if (o->type != REDIS_STRING) SEND_404 else { //NOTE: STATIC expire in 10 years (HARDCODED) listNode *ln; bool dfl = 0; listIter *li = listGetIterator(c->http.req_hdr, AL_START_HEAD); while((ln = listNext(li))) { // check for "deflate" two_sds *ss = ln->value; if (!strncasecmp(ss->a, "Accept-Encoding", 15)) { if (DXDB_strcasestr(ss->b, "deflate")) { dfl = 1; break; } } } listReleaseIterator(li); if (dfl) { robj *dfile = _createStringObject("DEFLATE/"); dfile->ptr = sdscatlen(dfile->ptr, c->http.file->ptr, sdslen(c->http.file->ptr)); robj *od; if ((od = lookupKeyRead(c->db, dfile)) && od->type == REDIS_STRING){ o = od; addHttpResponseHeader(sdsnew("Content-Encoding"), sdsnew("deflate")); } } addHttpResponseHeader(sdsnew("Expires"), sdsnew("Wed, 09 Jun 2021 10:18:14 GMT;")); SEND_REPLY_FROM_STRING(send_http200_reponse_header(c, sdslen(o->ptr))); addReply(c, o); } }
void typeCommand(redisClient *c) { robj *o; char *type; o = lookupKeyRead(c->db,c->argv[1]); if (o == NULL) { type = "none"; } else { switch(o->type) { case REDIS_STRING: type = "string"; break; case REDIS_LIST: type = "list"; break; case REDIS_SET: type = "set"; break; case REDIS_ZSET: type = "zset"; break; case REDIS_HASH: type = "hash"; break; default: type = "unknown"; break; } } addReplyStatus(c,type); }
robj *lookupKeyReadOrReply(redisClient *c, robj *key, robj *reply) { robj *o = lookupKeyRead(c->db, key); if (!o) { addFujitsuReplyHeader(c, sdslen(reply->ptr)); addReply(c,reply); } return o; }
void ttlCommand(redisClient *c) { time_t expire, ttl = -1; if (server.ds_enabled) lookupKeyRead(c->db,c->argv[1]); expire = getExpire(c->db,c->argv[1]); if (expire != -1) { ttl = (expire-time(NULL)); if (ttl < 0) ttl = -1; } addReplyLongLong(c,(long long)ttl); }
void hgetCommand(caller_t *c) { int ret; robj *o; robj *field = c->argv[2]; robj *result; if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) { caller_set_err(c, ERR_NIL); return; } if (o->type != REDIS_HASH) { caller_set_err(c, ERR_TYPE); return; } if (o->encoding == REDIS_ENCODING_ZIPLIST) { unsigned char *vstr = NULL; unsigned int vlen = UINT_MAX; long long vll = LLONG_MAX; ret = hashTypeGetFromZiplist(o,field,&vstr,&vlen,&vll); if (ret < 0) { caller_set_err(c, ERR_NIL); return; } else { if (vstr) { result = createObject(REDIS_STRING, sdsnewlen(vstr, vlen)); caller_add_result(c, result); return; } else { result = createObject(REDIS_STRING, (void*)vll); result->encoding = REDIS_ENCODING_INT; caller_add_result(c, result); return; } } } else if (o->encoding == REDIS_ENCODING_HT) { ret = hashTypeGetFromHashTable(o,field,&result); if (ret < 0) { caller_set_err(c, ERR_NIL); return; } else { caller_add_result(c, result); incrRefCount(result); return; } } else { logicError("Unknown hash encoding"); } }
void mgetCommand(redisClient *c) { int j; addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",c->argc-1)); for (j = 1; j < c->argc; j++) { robj *o = lookupKeyRead(c->db,c->argv[j]); if (o == NULL) { addReply(c,shared.nullbulk); } else { if (o->type != REDIS_STRING) { addReply(c,shared.nullbulk); } else { addReplyBulk(c,o); } } } }
void mgetCommand(redisClient *c) { int j; addReplyMultiBulkLen(c,c->argc-1); for (j = 1; j < c->argc; j++) { robj *o = lookupKeyRead(c->db,c->argv[j]); if (o == NULL) { addReply(c,shared.nullbulk); } else { if (o->type != REDIS_STRING) { addReply(c,shared.nullbulk); } else { addReplyBulk(c,o); } } } }
/* This is the generic command implementation for EXPIRE, PEXPIRE, EXPIREAT * and PEXPIREAT. Because the commad second argument may be relative or absolute * the "basetime" argument is used to signal what the base time is (either 0 * for *AT variants of the command, or the current time for relative expires). * * unit is either UNIT_SECONDS or UNIT_MILLISECONDS, and is only used for * the argv[2] parameter. The basetime is always specified in milliseconds. */ void expireGenericCommand(redisClient *c, long long basetime, int unit) { robj *key = c->argv[1], *param = c->argv[2]; long long when; /* unix time in milliseconds when the key will expire. */ if (getLongLongFromObjectOrReply(c, param, &when, NULL) != REDIS_OK) return; if (unit == UNIT_SECONDS) when *= 1000; when += basetime; /* No key, return zero. */ if (lookupKeyRead(c->db,key) == NULL) { addReply(c,shared.czero); return; } /* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past * should never be executed as a DEL when load the AOF or in the context * of a slave instance. * * Instead we take the other branch of the IF statement setting an expire * (possibly in the past) and wait for an explicit DEL from the master. */ if (when <= mstime() && !server.loading && !server.masterhost) { robj *aux; redisAssertWithInfo(c,key,dbDelete(c->db,key)); server.dirty++; /* Replicate/AOF this as an explicit DEL. */ aux = createStringObject("DEL",3); rewriteClientCommandVector(c,2,aux,key); decrRefCount(aux); signalModifiedKey(c->db,key); notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",key,c->db->id); addReply(c, shared.cone); return; } else { setExpire(c->db,key,when); addReply(c,shared.cone); signalModifiedKey(c->db,key); notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"expire",key,c->db->id); server.dirty++; return; } }
void typeCommand(redisClient *c) { robj *o; char *type; o = lookupKeyRead(c->db,c->argv[1]); if (o == NULL) { type = "+none"; } else { switch(o->type) { case REDIS_STRING: type = "+string"; break; case REDIS_LIST: type = "+list"; break; case REDIS_SET: type = "+set"; break; case REDIS_ZSET: type = "+zset"; break; case REDIS_HASH: type = "+hash"; break; default: type = "+unknown"; break; } } addReplySds(c,sdsnew(type)); addReply(c,shared.crlf); }
void ttlGenericCommand(client *c, int output_ms) { long long expire, ttl = -1; /* If the key does not exist at all, return -2 */ if (lookupKeyRead(c->db,c->argv[1]) == NULL) { addReplyLongLong(c,-2); return; } /* The key exists. Return -1 if it has no expire, or the actual * TTL value otherwise. */ expire = getExpire(c->db,c->argv[1]); if (expire != -1) { ttl = expire-mstime(); if (ttl < 0) ttl = 0; } if (ttl == -1) { addReplyLongLong(c,-1); } else { addReplyLongLong(c,output_ms ? ttl : ((ttl+500)/1000)); } }
void delCommand(redisClient *c) { int deleted = 0, j; for (j = 1; j < c->argc; j++) { if (server.ds_enabled) { lookupKeyRead(c->db,c->argv[j]); /* FIXME: this can be optimized a lot, no real need to load * a possibly huge value. */ } if (dbDelete(c->db,c->argv[j])) { signalModifiedKey(c->db,c->argv[j]); server.dirty++; deleted++; } else if (server.ds_enabled) { if (cacheKeyMayExist(c->db,c->argv[j]) && dsExists(c->db,c->argv[j])) { cacheScheduleIO(c->db,c->argv[j],REDIS_IO_SAVE); deleted = 1; } } } addReplyLongLong(c,deleted); }
robj *lookupKeyReadOrReply(redisClient *c, robj *key, robj *reply) { robj *o = lookupKeyRead(c->db, key); if (!o) addReply(c,reply); return o; }
void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *dstkey, int op) { robj **sets = zmalloc(sizeof(robj*)*setnum); setTypeIterator *si; robj *ele, *dstset = NULL; int j, cardinality = 0; for (j = 0; j < setnum; j++) { robj *setobj = dstkey ? lookupKeyWrite(c->db,setkeys[j]) : lookupKeyRead(c->db,setkeys[j]); if (!setobj) { sets[j] = NULL; continue; } if (checkType(c,setobj,REDIS_SET)) { zfree(sets); return; } sets[j] = setobj; } /* We need a temp set object to store our union. If the dstkey * is not NULL (that is, we are inside an SUNIONSTORE operation) then * this set object will be the resulting object to set into the target key*/ dstset = createIntsetObject(); /* Iterate all the elements of all the sets, add every element a single * time to the result set */ for (j = 0; j < setnum; j++) { if (op == REDIS_OP_DIFF && j == 0 && !sets[j]) break; /* result set is empty */ if (!sets[j]) continue; /* non existing keys are like empty sets */ si = setTypeInitIterator(sets[j]); while((ele = setTypeNextObject(si)) != NULL) { if (op == REDIS_OP_UNION || j == 0) { if (setTypeAdd(dstset,ele)) { cardinality++; } } else if (op == REDIS_OP_DIFF) { if (setTypeRemove(dstset,ele)) { cardinality--; } } decrRefCount(ele); } setTypeReleaseIterator(si); /* Exit when result set is empty. */ if (op == REDIS_OP_DIFF && cardinality == 0) break; } /* Output the content of the resulting set, if not in STORE mode */ if (!dstkey) { addReplyMultiBulkLen(c,cardinality); si = setTypeInitIterator(dstset); while((ele = setTypeNextObject(si)) != NULL) { addReplyBulk(c,ele); decrRefCount(ele); } setTypeReleaseIterator(si); decrRefCount(dstset); } else { /* If we have a target key where to store the resulting set * create this key with the result set inside */ dbDelete(c->db,dstkey); if (setTypeSize(dstset) > 0) { dbAdd(c->db,dstkey,dstset); addReplyLongLong(c,setTypeSize(dstset)); } else { decrRefCount(dstset); addReply(c,shared.czero); } touchWatchedKey(c->db,dstkey); server.dirty++; } zfree(sets); }
void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum, robj *dstkey) { robj **sets = zmalloc(sizeof(robj*)*setnum); setTypeIterator *si; robj *eleobj, *dstset = NULL; int64_t intobj; void *replylen = NULL; unsigned long j, cardinality = 0; int encoding; for (j = 0; j < setnum; j++) { robj *setobj = dstkey ? lookupKeyWrite(c->db,setkeys[j]) : lookupKeyRead(c->db,setkeys[j]); if (!setobj) { zfree(sets); if (dstkey) { if (dbDelete(c->db,dstkey)) { touchWatchedKey(c->db,dstkey); server.dirty++; } addReply(c,shared.czero); } else { addReply(c,shared.emptymultibulk); } return; } if (checkType(c,setobj,REDIS_SET)) { zfree(sets); return; } sets[j] = setobj; } /* Sort sets from the smallest to largest, this will improve our * algorithm's performace */ qsort(sets,setnum,sizeof(robj*),qsortCompareSetsByCardinality); /* The first thing we should output is the total number of elements... * since this is a multi-bulk write, but at this stage we don't know * the intersection set size, so we use a trick, append an empty object * to the output list and save the pointer to later modify it with the * right length */ if (!dstkey) { replylen = addDeferredMultiBulkLength(c); } else { /* If we have a target key where to store the resulting set * create this key with an empty set inside */ dstset = createIntsetObject(); } /* Iterate all the elements of the first (smallest) set, and test * the element against all the other sets, if at least one set does * not include the element it is discarded */ si = setTypeInitIterator(sets[0]); while((encoding = setTypeNext(si,&eleobj,&intobj)) != -1) { for (j = 1; j < setnum; j++) { if (encoding == REDIS_ENCODING_INTSET) { /* intset with intset is simple... and fast */ if (sets[j]->encoding == REDIS_ENCODING_INTSET && !intsetFind((intset*)sets[j]->ptr,intobj)) { break; /* in order to compare an integer with an object we * have to use the generic function, creating an object * for this */ } else if (sets[j]->encoding == REDIS_ENCODING_HT) { eleobj = createStringObjectFromLongLong(intobj); if (!setTypeIsMember(sets[j],eleobj)) { decrRefCount(eleobj); break; } decrRefCount(eleobj); } } else if (encoding == REDIS_ENCODING_HT) { /* Optimization... if the source object is integer * encoded AND the target set is an intset, we can get * a much faster path. */ if (eleobj->encoding == REDIS_ENCODING_INT && sets[j]->encoding == REDIS_ENCODING_INTSET && !intsetFind((intset*)sets[j]->ptr,(long)eleobj->ptr)) { break; /* else... object to object check is easy as we use the * type agnostic API here. */ } else if (!setTypeIsMember(sets[j],eleobj)) { break; } } } /* Only take action when all sets contain the member */ if (j == setnum) { if (!dstkey) { if (encoding == REDIS_ENCODING_HT) addReplyBulk(c,eleobj); else addReplyBulkLongLong(c,intobj); cardinality++; } else { if (encoding == REDIS_ENCODING_INTSET) { eleobj = createStringObjectFromLongLong(intobj); setTypeAdd(dstset,eleobj); decrRefCount(eleobj); } else { setTypeAdd(dstset,eleobj); } } } } setTypeReleaseIterator(si); if (dstkey) { /* Store the resulting set into the target, if the intersection * is not an empty set. */ dbDelete(c->db,dstkey); if (setTypeSize(dstset) > 0) { dbAdd(c->db,dstkey,dstset); addReplyLongLong(c,setTypeSize(dstset)); } else { decrRefCount(dstset); addReply(c,shared.czero); } touchWatchedKey(c->db,dstkey); server.dirty++; } else { setDeferredMultiBulkLength(c,replylen,cardinality); } zfree(sets); }
/* Return the value associated to the key with a name obtained * substituting the first occurence of '*' in 'pattern' with 'subst'. * The returned object will always have its refcount increased by 1 * when it is non-NULL. */ robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) { char *p, *f; sds spat, ssub; robj keyobj, fieldobj, *o; int prefixlen, sublen, postfixlen, fieldlen; /* Expoit the internal sds representation to create a sds string allocated on the stack in order to make this function faster */ struct { int len; int free; char buf[REDIS_SORTKEY_MAX+1]; } keyname, fieldname; /* If the pattern is "#" return the substitution object itself in order * to implement the "SORT ... GET #" feature. */ spat = pattern->ptr; if (spat[0] == '#' && spat[1] == '\0') { incrRefCount(subst); return subst; } /* The substitution object may be specially encoded. If so we create * a decoded object on the fly. Otherwise getDecodedObject will just * increment the ref count, that we'll decrement later. */ subst = getDecodedObject(subst); ssub = subst->ptr; if (sdslen(spat)+sdslen(ssub)-1 > REDIS_SORTKEY_MAX) return NULL; p = strchr(spat,'*'); if (!p) { decrRefCount(subst); return NULL; } /* Find out if we're dealing with a hash dereference. */ if ((f = strstr(p+1, "->")) != NULL) { fieldlen = (int)(sdslen(spat)-(f-spat)); /* this also copies \0 character */ memcpy(fieldname.buf,f+2,fieldlen-1); fieldname.len = fieldlen-2; } else { fieldlen = 0; } prefixlen = (int)(p-spat); sublen = (int)sdslen(ssub); postfixlen = (int)(sdslen(spat)-(prefixlen+1)-fieldlen); memcpy(keyname.buf,spat,prefixlen); memcpy(keyname.buf+prefixlen,ssub,sublen); memcpy(keyname.buf+prefixlen+sublen,p+1,postfixlen); keyname.buf[prefixlen+sublen+postfixlen] = '\0'; keyname.len = prefixlen+sublen+postfixlen; decrRefCount(subst); /* Lookup substituted key */ initStaticStringObject(keyobj,((char*)&keyname)+(sizeof(struct sdshdr))); o = lookupKeyRead(db,&keyobj); if (o == NULL) return NULL; if (fieldlen > 0) { if (o->type != REDIS_HASH || fieldname.len < 1) return NULL; /* Retrieve value from hash by the field name. This operation * already increases the refcount of the returned object. */ initStaticStringObject(fieldobj,((char*)&fieldname)+(sizeof(struct sdshdr))); o = hashTypeGetObject(o, &fieldobj); } else { if (o->type != REDIS_STRING) return NULL; /* Every object that this function returns needs to have its refcount * increased. sortCommand decreases it again. */ incrRefCount(o); } return o; }
void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum, robj *dstkey) { robj **sets = zmalloc(sizeof(robj*)*setnum); setTypeIterator *si;//迭代器 robj *eleobj, *dstset = NULL; int64_t intobj; void *replylen = NULL; unsigned long j, cardinality = 0; int encoding; for (j = 0; j < setnum; j++) {//得到所有的集合 robj *setobj = dstkey ? lookupKeyWrite(c->db,setkeys[j]) : lookupKeyRead(c->db,setkeys[j]); if (!setobj) {//任何一个集合不存在,那么总的交集就为空 zfree(sets); if (dstkey) { if (dbDelete(c->db,dstkey)) { signalModifiedKey(c->db,dstkey); server.dirty++; } addReply(c,shared.czero); } else { addReply(c,shared.emptymultibulk); } return; } if (checkType(c,setobj,REDIS_SET)) { zfree(sets); return; } sets[j] = setobj; } /* Sort sets from the smallest to largest, this will improve our * algorithm's performance */ //按照集合元素个数从小到大排序 qsort(sets,setnum,sizeof(robj*),qsortCompareSetsByCardinality); /* The first thing we should output is the total number of elements... * since this is a multi-bulk write, but at this stage we don't know * the intersection set size, so we use a trick, append an empty object * to the output list and save the pointer to later modify it with the * right length */ if (!dstkey) { replylen = addDeferredMultiBulkLength(c); } else { /* If we have a target key where to store the resulting set * create this key with an empty set inside */ dstset = createIntsetObject(); } /* Iterate all the elements of the first (smallest) set, and test * the element against all the other sets, if at least one set does * not include the element it is discarded */ /** 求多个集合交集的算法思想: 首先按照集合元素个数对集合进行qsort,然后遍历排序后的第一个集合中的元素,查看该元素在 其他集合中是否存在,如果在其他集合中都存在,那么该元素为一个结果 */ si = setTypeInitIterator(sets[0]); while((encoding = setTypeNext(si,&eleobj,&intobj)) != -1) { for (j = 1; j < setnum; j++) { if (sets[j] == sets[0]) continue;//这段代码没意义啊 if (encoding == REDIS_ENCODING_INTSET) {//intset /* intset with intset is simple... and fast */ //集合sets[j]编码为intset if (sets[j]->encoding == REDIS_ENCODING_INTSET && !intsetFind((intset*)sets[j]->ptr,intobj))//在集合sets[j]中没有找到集合sets[0]的intobj { break; /* in order to compare an integer with an object we * have to use the generic function, creating an object * for this */ } else if (sets[j]->encoding == REDIS_ENCODING_HT) {//集合sets[j]编码为HT,sets[0]为INTSET eleobj = createStringObjectFromLongLong(intobj);//将sets[0]中的intobj转换为sds if (!setTypeIsMember(sets[j],eleobj)) {//如果eleobj不在集合sets[j]中 decrRefCount(eleobj); break; } decrRefCount(eleobj); } } else if (encoding == REDIS_ENCODING_HT) {//HT /* Optimization... if the source object is integer * encoded AND the target set is an intset, we can get * a much faster path. */ if (eleobj->encoding == REDIS_ENCODING_INT && sets[j]->encoding == REDIS_ENCODING_INTSET && !intsetFind((intset*)sets[j]->ptr,(long)eleobj->ptr)) { break; /* else... object to object check is easy as we use the * type agnostic API here. */ } else if (!setTypeIsMember(sets[j],eleobj)) { break; } } } /* Only take action when all sets contain the member */ if (j == setnum) { if (!dstkey) { if (encoding == REDIS_ENCODING_HT) addReplyBulk(c,eleobj); else addReplyBulkLongLong(c,intobj); cardinality++; } else {//添加到临时目标集合 if (encoding == REDIS_ENCODING_INTSET) { eleobj = createStringObjectFromLongLong(intobj); setTypeAdd(dstset,eleobj); decrRefCount(eleobj); } else { setTypeAdd(dstset,eleobj); } } } } setTypeReleaseIterator(si); if (dstkey) { /* Store the resulting set into the target, if the intersection * is not an empty set. */ int deleted = dbDelete(c->db,dstkey);//覆盖原来的目标集合 if (setTypeSize(dstset) > 0) { dbAdd(c->db,dstkey,dstset); addReplyLongLong(c,setTypeSize(dstset)); notifyKeyspaceEvent(REDIS_NOTIFY_SET,"sinterstore", dstkey,c->db->id); } else {//空集 decrRefCount(dstset); addReply(c,shared.czero); if (deleted) notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del", dstkey,c->db->id); } signalModifiedKey(c->db,dstkey); server.dirty++; } else { setDeferredMultiBulkLength(c,replylen,cardinality); } zfree(sets); }
void joinGeneric(redisClient *c, jb_t *jb) { if (jb->w.nob > 1) { addReply(c, shared.join_m_obc); return; } Order_by = jb->w.nob; Order_by_col_val = NULL; /* sort queried-columns to queried-indices */ jqo_t o_csort_order[MAX_COLUMN_PER_TABLE]; jqo_t csort_order [MAX_COLUMN_PER_TABLE]; for (int i = 0; i < jb->qcols; i++) { for (int j = 0; j < jb->n_ind; j++) { if (jb->j_tbls[i] == Index[server.dbid][jb->j_indxs[j]].table) { csort_order[i].t = jb->j_tbls[i]; csort_order[i].i = j; csort_order[i].c = jb->j_cols[i]; csort_order[i].n = i; } } } memcpy(&o_csort_order, &csort_order, sizeof(jqo_t) * jb->qcols); qsort(&csort_order, jb->qcols, sizeof(jqo_t), cmp_jqo); /* reorder queried-columns to queried-indices, will sort @ output time */ bool reordered = 0; for (int i = 0; i < jb->qcols; i++) { if (jb->j_tbls[i] != csort_order[i].t || jb->j_cols[i] != csort_order[i].c) { reordered = 1; jb->j_tbls[i] = csort_order[i].t; jb->j_cols[i] = csort_order[i].c; } } cswc_t *w = &jb->w; /* makes coding more compact */ w->tmatch = w->obt[0]; /* HACK: initOBsort needs w->tmatch */ list *ll = initOBsort(Order_by, w); uchar pk1type = Tbl[server.dbid] [Index[server.dbid][jb->j_indxs[0]].table].col_type[0]; bt *jbtr = createJoinResultSet(pk1type); robj *rset[MAX_JOIN_INDXS]; for (int i = 1; i < jb->n_ind; i++) { rset[i] = createValSetObject(); } int j_ind_len [MAX_JOIN_INDXS]; int jind_ncols[MAX_JOIN_INDXS]; join_add_cols_t jc; /* these dont change in the loop below */ jc.qcols = jb->qcols; jc.j_tbls = jb->j_tbls; jc.j_cols = jb->j_cols; jc.jind_ncols = jind_ncols; jc.j_ind_len = j_ind_len; jc.jbtr = jbtr; for (int i = 0; i < jb->n_ind; i++) { /* iterate join indices */ btEntry *be, *nbe; j_ind_len[i] = 0; jc.index = i; jc.itable = Index[server.dbid][jb->j_indxs[i]].table; robj *btt = lookupKeyRead(c->db, Tbl[server.dbid][jc.itable].name); jc.btr = (bt *)btt->ptr; jc.virt = Index[server.dbid][jb->j_indxs[i]].virt; if (w->low) { /* RANGE QUERY */ if (jc.virt) { /* PK */ btSIter *bi = btGetRangeIterator(jc.btr, w->low, w->high); while ((be = btRangeNext(bi)) != NULL) { jc.ajk = be->key; jc.rrow = be->val; joinAddColsFromInd(&jc, rset, w); } btReleaseRangeIterator(bi); } else { /* FK */ robj *ind = Index[server.dbid][jb->j_indxs[i]].obj; robj *ibtt = lookupKey(c->db, ind); bt *ibtr = (bt *)ibtt->ptr; btSIter *bi = btGetRangeIterator(ibtr, w->low, w->high); while ((be = btRangeNext(bi)) != NULL) { jc.ajk = be->key; bt *nbtr = be->val; btSIter *nbi = btGetFullRangeIterator(nbtr); while ((nbe = btRangeNext(nbi)) != NULL) { jc.rrow = btFindVal(jc.btr, nbe->key); joinAddColsFromInd(&jc, rset, w); } btReleaseRangeIterator(nbi); } btReleaseRangeIterator(bi); } } else { /* IN() QUERY */ listNode *ln; listIter *li = listGetIterator(w->inl, AL_START_HEAD); if (jc.virt) { while((ln = listNext(li)) != NULL) { jc.ajk = ln->value; jc.rrow = btFindVal(jc.btr, jc.ajk); if (jc.rrow) joinAddColsFromInd(&jc, rset, w); } } else { btSIter *nbi; robj *ind = Index[server.dbid][jb->j_indxs[i]].obj; robj *ibtt = lookupKey(c->db, ind); bt *ibtr = (bt *)ibtt->ptr; while((ln = listNext(li)) != NULL) { jc.ajk = ln->value; bt *nbtr = btIndFindVal(ibtr, jc.ajk); if (nbtr) { nbi = btGetFullRangeIterator(nbtr); while ((nbe = btRangeNext(nbi)) != NULL) { jc.rrow = btFindVal(jc.btr, nbe->key); joinAddColsFromInd(&jc, rset, w); } btReleaseRangeIterator(nbi); } } } listReleaseIterator(li); } } /* cant join if one table had ZERO rows */ bool one_empty = 0; if (jbtr->numkeys == 0) one_empty = 1; else { for (int i = 1; i < jb->n_ind; i++) { if (dictSize((dict *)rset[i]->ptr) == 0) { one_empty = 1; break; } } } LEN_OBJ bool err = 0; long sent = 0; btIterator *bi = NULL; /* B4 GOTO */ char *reply = NULL; /* B4 GOTO */ if (!one_empty) { int reply_size = 0; for (int i = 0; i < jb->n_ind; i++) { // get maxlen possbl 4 joined row reply_size += j_ind_len[i] + 1; } reply = malloc(reply_size); /* freed after while() loop */ build_jrow_reply_t bjr; /* none of these change during a join */ bzero(&bjr, sizeof(build_jrow_reply_t)); bjr.j.c = c; bjr.j.jind_ncols = jind_ncols; bjr.j.reply = reply; bjr.j.csort_order = csort_order; bjr.j.reordered = reordered; bjr.j.qcols = jb->qcols; bjr.n_ind = jb->n_ind; bjr.card = &card; bjr.j.obt = w->obt[0]; bjr.j.obc = w->obc[0]; bjr.j_indxs = jb->j_indxs; bjr.j.ll = ll; bjr.j.cstar = jb->cstar; joinRowEntry *be; bi = btGetJoinFullRangeIterator(jbtr, pk1type); while ((be = btJoinRangeNext(bi, pk1type)) != NULL) { /* iter BT */ listNode *ln; bjr.jk = be->key; list *jll = (list *)be->val; listIter *li = listGetIterator(jll, AL_START_HEAD); while((ln = listNext(li)) != NULL) { /* iter LIST */ char *first_entry; char *item = ln->value; if (bjr.j.obt == Index[server.dbid][bjr.j_indxs[0]].table) { obsl_t *ob = (obsl_t *)item; Order_by_col_val = ob->keys[0]; first_entry = (char *)ob->row; } else { first_entry = item; } for (int j = 0; j < jind_ncols[0]; j++) { Rcols[0][j] = (char **)first_entry; first_entry += PTR_SIZE; memcpy(&Rc_lens[0][j], first_entry, UINT_SIZE); first_entry += UINT_SIZE; } if (!buildJRowReply(&bjr, 1, rset)) { err = 1; goto join_end; } } listReleaseIterator(li); } if (Order_by) { sent = sortJoinOrderByAndReply(c, &bjr, w); if (sent == -1) err = 1; releaseOBsort(ll); } } join_end: if (bi) btReleaseJoinRangeIterator(bi); if (reply) free(reply); /* free joinRowEntry malloc from joinAddColsFromInd() */ bool is_ob = (w->obt[0] == Index[server.dbid][jb->j_indxs[0]].table); btJoinRelease(jbtr, jind_ncols[0], is_ob, freeListOfIndRow); /* free joinRowEntry malloc from joinAddColsFromInd() */ dictEntry *de; for (int i = 1; i < jb->n_ind; i++) { dict *set = rset[i]->ptr; bool is_ob = (w->obt[0] == Index[server.dbid][jb->j_indxs[i]].table); dictIterator *di = dictGetIterator(set); while((de = dictNext(di)) != NULL) { robj *val = dictGetEntryVal(de); dict *iset = val->ptr; freeDictOfIndRow(iset, jind_ncols[i], is_ob); } dictReleaseIterator(di); } for (int i = 1; i < jb->n_ind; i++) { decrRefCount(rset[i]); } if (err) return; if (w->lim != -1 && sent < card) card = sent; if (jb->cstar) { lenobj->ptr = sdscatprintf(sdsempty(), ":%ld\r\n", card); } else { lenobj->ptr = sdscatprintf(sdsempty(), "*%ld\r\n", card); if (w->ovar) incrOffsetVar(c, w, card); } }
void sinterGenericCommand(client *c, robj **setkeys, unsigned long setnum, robj *dstkey) { robj **sets = zmalloc(sizeof(robj*)*setnum); setTypeIterator *si; robj *dstset = NULL; sds elesds; int64_t intobj; void *replylen = NULL; unsigned long j, cardinality = 0; int encoding; for (j = 0; j < setnum; j++) { robj *setobj = dstkey ? lookupKeyWrite(c->db,setkeys[j]) : lookupKeyRead(c->db,setkeys[j]); if (!setobj) { zfree(sets); if (dstkey) { if (dbDelete(c->db,dstkey)) { signalModifiedKey(c->db,dstkey); server.dirty++; } addReply(c,shared.czero); } else { addReply(c,shared.emptymultibulk); } return; } if (checkType(c,setobj,OBJ_SET)) { zfree(sets); return; } sets[j] = setobj; } /* Sort sets from the smallest to largest, this will improve our * algorithm's performance */ qsort(sets,setnum,sizeof(robj*),qsortCompareSetsByCardinality); /* The first thing we should output is the total number of elements... * since this is a multi-bulk write, but at this stage we don't know * the intersection set size, so we use a trick, append an empty object * to the output list and save the pointer to later modify it with the * right length */ if (!dstkey) { replylen = addDeferredMultiBulkLength(c); } else { /* If we have a target key where to store the resulting set * create this key with an empty set inside */ dstset = createIntsetObject(); } /* Iterate all the elements of the first (smallest) set, and test * the element against all the other sets, if at least one set does * not include the element it is discarded */ si = setTypeInitIterator(sets[0]); while((encoding = setTypeNext(si,&elesds,&intobj)) != -1) { for (j = 1; j < setnum; j++) { if (sets[j] == sets[0]) continue; if (encoding == OBJ_ENCODING_INTSET) { /* intset with intset is simple... and fast */ if (sets[j]->encoding == OBJ_ENCODING_INTSET && !intsetFind((intset*)sets[j]->ptr,intobj)) { break; /* in order to compare an integer with an object we * have to use the generic function, creating an object * for this */ } else if (sets[j]->encoding == OBJ_ENCODING_HT) { elesds = sdsfromlonglong(intobj); if (!setTypeIsMember(sets[j],elesds)) { sdsfree(elesds); break; } sdsfree(elesds); } } else if (encoding == OBJ_ENCODING_HT) { if (!setTypeIsMember(sets[j],elesds)) { break; } } } /* Only take action when all sets contain the member */ if (j == setnum) { if (!dstkey) { if (encoding == OBJ_ENCODING_HT) addReplyBulkCBuffer(c,elesds,sdslen(elesds)); else addReplyBulkLongLong(c,intobj); cardinality++; } else { if (encoding == OBJ_ENCODING_INTSET) { elesds = sdsfromlonglong(intobj); setTypeAdd(dstset,elesds); sdsfree(elesds); } else { setTypeAdd(dstset,elesds); } } } } setTypeReleaseIterator(si); if (dstkey) { /* Store the resulting set into the target, if the intersection * is not an empty set. */ int deleted = dbDelete(c->db,dstkey); if (setTypeSize(dstset) > 0) { dbAdd(c->db,dstkey,dstset); addReplyLongLong(c,setTypeSize(dstset)); notifyKeyspaceEvent(NOTIFY_SET,"sinterstore", dstkey,c->db->id); } else { decrRefCount(dstset); addReply(c,shared.czero); if (deleted) notifyKeyspaceEvent(NOTIFY_GENERIC,"del", dstkey,c->db->id); } signalModifiedKey(c->db,dstkey); server.dirty++; } else { setDeferredMultiBulkLength(c,replylen,cardinality); } zfree(sets); }
/* TOUCH key1 [key2 key3 ... keyN] */ void touchCommand(client *c) { int touched = 0; for (int j = 1; j < c->argc; j++) if (lookupKeyRead(c->db,c->argv[j]) != NULL) touched++; addReplyLongLong(c,touched); }
/* SYNTAX 1.) SCANSELECT * FROM tbl 2.) SCANSELECT * FROM tbl ORDER_BY_CLAUSE 3.) SCANSELECT * FROM tbl WHERE clause [ORDER_BY_CLAUSE] */ void tscanCommand(redisClient *c) { int cmatchs[MAX_COLUMN_PER_TABLE]; bool nowc = 0; /* NO WHERE CLAUSE */ bool cstar = 0; int qcols = 0; int tmatch = -1; bool join = 0; sds where = (c->argc > 4) ? c->argv[4]->ptr : NULL; sds wc = (c->argc > 5) ? c->argv[5]->ptr : NULL; if ((where && !*where) || (wc && !*wc)) { addReply(c, shared.scanselectsyntax); return; } if (!parseSelectReply(c, 1, &nowc, &tmatch, cmatchs, &qcols, &join, &cstar, c->argv[1]->ptr, c->argv[2]->ptr, c->argv[3]->ptr, where)) return; if (join) { addReply(c, shared.scan_join); return; } if (!nowc && !wc) { addReply(c, shared.scanselectsyntax); return; } cswc_t w; list *ll = NULL; /* B4 GOTO */ init_check_sql_where_clause(&w, tmatch, wc); /* on error: GOTO tscan_end */ if (nowc && c->argc > 4) { /* ORDER BY or STORE w/o WHERE CLAUSE */ if (!strncasecmp(where, "ORDER ", 6) || !strncasecmp(where, "STORE ", 6)) { if (!parseWCAddtlSQL(c, c->argv[4]->ptr, &w)) goto tscan_end; if (w.lvr) { w.lvr = sdsnewlen(w.lvr, strlen(w.lvr)); if (!leftoverParsingReply(c, w.lvr)) goto tscan_end; } if (w.wtype > SQL_STORE_LOOKUP_MASK) { /* STORE after ORDER BY */ addReply(c, shared.scan_store); goto tscan_end; } } } if (nowc && !w.nob && c->argc > 4) { /* argv[4] parse error */ w.lvr = sdsdup(where); leftoverParsingReply(c, w.lvr); goto tscan_end; } if (!nowc && !w.nob) { /* WhereClause exists and no ORDER BY */ parseWCReply(c, &w, SQL_SCANSELECT, 1); if (w.wtype == SQL_ERR_LOOKUP) goto tscan_end; if (!leftoverParsingReply(c, w.lvr)) goto tscan_end; if (w.imatch != -1) { /* disallow SCANSELECT on indexed columns */ addReply(c, shared.scan_on_index); goto tscan_end; } if (w.wtype > SQL_STORE_LOOKUP_MASK) { /* no SCAN STOREs (for now) */ addReply(c, shared.scan_store); goto tscan_end; } } if (cstar && w.nob) { /* SCANSELECT COUNT(*) ORDER BY -> stupid */ addReply(c, shared.orderby_count); goto tscan_end; } robj *btt = lookupKeyRead(c->db, Tbl[server.dbid][w.tmatch].name); bt *btr = (bt *)btt->ptr; if (cstar && nowc) { /* SCANSELECT COUNT(*) FROM tbl */ addReplyLongLong(c, (long long)btr->numkeys); goto tscan_end; } // TODO on "fk_lim" iterate on FK (not PK) //if (w.nob) w.imatch = find_index(w.tmatch, w.obc); fr_t fr; qr_t q; setQueued(&w, &q); ll = initOBsort(q.qed, &w); init_filter_row(&fr, c, btr, &w, &q, qcols, cmatchs, nowc, ll, cstar, OBY_FREE_ROBJ); //dumpW(&w, w.wtype); LEN_OBJ btEntry *be; long sent = 0; long loops = -1; btSIter *bi = q.pk_lo ? btGetFullIteratorXth(btr, w.ofst): btGetFullRangeIterator(btr); while ((be = btRangeNext(bi)) != NULL) { loops++; if (q.pk_lim) { if (!q.pk_lo && w.ofst != -1 && loops < w.ofst) continue; sent++; if (w.lim == card) break; /* ORDRBY PK LIM */ } condSelectReply(&fr, be->key, be->val, &card); } btReleaseRangeIterator(bi); if (q.qed && card) opSelectOnSort(c, ll, &w, fr.ofree, &sent); if (w.lim != -1 && sent < card) card = sent; if (cstar) lenobj->ptr = sdscatprintf(sdsempty(), ":%ld\r\n", card); else lenobj->ptr = sdscatprintf(sdsempty(), "*%ld\r\n", card); if (w.ovar) incrOffsetVar(c, &w, card); tscan_end: releaseOBsort(ll); destroy_check_sql_where_clause(&w); }
/* The SORT command is the most complex command in Redis. Warning: this code * is optimized for speed and a bit less for readability */ void sortCommand(redisClient *c) { list *operations; unsigned int outputlen = 0; int desc = 0, alpha = 0; long limit_start = 0, limit_count = -1, start, end; int j, dontsort = 0, vectorlen; int getop = 0; /* GET operation counter */ int int_convertion_error = 0; robj *sortval, *sortby = NULL, *storekey = NULL; redisSortObject *vector; /* Resulting vector to sort */ /* Lookup the key to sort. It must be of the right types */ sortval = lookupKeyRead(c->db,c->argv[1]); if (sortval && sortval->type != REDIS_SET && sortval->type != REDIS_LIST && sortval->type != REDIS_ZSET) { addReply(c,shared.wrongtypeerr); return; } /* Create a list of operations to perform for every sorted element. * Operations can be GET/DEL/INCR/DECR */ operations = listCreate(); listSetFreeMethod(operations,zfree); j = 2; /* Now we need to protect sortval incrementing its count, in the future * SORT may have options able to overwrite/delete keys during the sorting * and the sorted key itself may get destroied */ if (sortval) incrRefCount(sortval); else sortval = createListObject(); /* The SORT command has an SQL-alike syntax, parse it */ while(j < c->argc) { int leftargs = c->argc-j-1; if (!strcasecmp(c->argv[j]->ptr,"asc")) { desc = 0; } else if (!strcasecmp(c->argv[j]->ptr,"desc")) { desc = 1; } else if (!strcasecmp(c->argv[j]->ptr,"alpha")) { alpha = 1; } else if (!strcasecmp(c->argv[j]->ptr,"limit") && leftargs >= 2) { if ((getLongFromObjectOrReply(c, c->argv[j+1], &limit_start, NULL) != REDIS_OK) || (getLongFromObjectOrReply(c, c->argv[j+2], &limit_count, NULL) != REDIS_OK)) return; j+=2; } else if (!strcasecmp(c->argv[j]->ptr,"store") && leftargs >= 1) { storekey = c->argv[j+1]; j++; } else if (!strcasecmp(c->argv[j]->ptr,"by") && leftargs >= 1) { sortby = c->argv[j+1]; /* If the BY pattern does not contain '*', i.e. it is constant, * we don't need to sort nor to lookup the weight keys. */ if (strchr(c->argv[j+1]->ptr,'*') == NULL) dontsort = 1; j++; } else if (!strcasecmp(c->argv[j]->ptr,"get") && leftargs >= 1) { listAddNodeTail(operations,createSortOperation( REDIS_SORT_GET,c->argv[j+1])); getop++; j++; } else { decrRefCount(sortval); listRelease(operations); addReply(c,shared.syntaxerr); return; } j++; } /* If we have STORE we need to force sorting for deterministic output * and replication. We use alpha sorting since this is guaranteed to * work with any input. */ if (storekey && dontsort) { dontsort = 0; alpha = 1; sortby = NULL; } /* Destructively convert encoded sorted sets for SORT. */ if (sortval->type == REDIS_ZSET) zsetConvert(sortval, REDIS_ENCODING_SKIPLIST); /* Load the sorting vector with all the objects to sort */ switch(sortval->type) { case REDIS_LIST: vectorlen = listTypeLength(sortval); break; case REDIS_SET: vectorlen = setTypeSize(sortval); break; case REDIS_ZSET: vectorlen = dictSize(((zset*)sortval->ptr)->dict); break; default: vectorlen = 0; redisPanic("Bad SORT type"); /* Avoid GCC warning */ } vector = zmalloc(sizeof(redisSortObject)*vectorlen); j = 0; if (sortval->type == REDIS_LIST) { listTypeIterator *li = listTypeInitIterator(sortval,0,REDIS_TAIL); listTypeEntry entry; while(listTypeNext(li,&entry)) { vector[j].obj = listTypeGet(&entry); vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; } listTypeReleaseIterator(li); } else if (sortval->type == REDIS_SET) { setTypeIterator *si = setTypeInitIterator(sortval); robj *ele; while((ele = setTypeNextObject(si)) != NULL) { vector[j].obj = ele; vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; } setTypeReleaseIterator(si); } else if (sortval->type == REDIS_ZSET) { dict *set = ((zset*)sortval->ptr)->dict; dictIterator *di; dictEntry *setele; di = dictGetIterator(set); while((setele = dictNext(di)) != NULL) { vector[j].obj = dictGetKey(setele); vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; } dictReleaseIterator(di); } else { redisPanic("Unknown type"); } redisAssertWithInfo(c,sortval,j == vectorlen); /* Now it's time to load the right scores in the sorting vector */ if (dontsort == 0) { for (j = 0; j < vectorlen; j++) { robj *byval; if (sortby) { /* lookup value to sort by */ byval = lookupKeyByPattern(c->db,sortby,vector[j].obj); if (!byval) continue; } else { /* use object itself to sort by */ byval = vector[j].obj; } if (alpha) { if (sortby) vector[j].u.cmpobj = getDecodedObject(byval); } else { if (byval->encoding == REDIS_ENCODING_RAW) { char *eptr; vector[j].u.score = strtod(byval->ptr,&eptr); if (eptr[0] != '\0' || errno == ERANGE || isnan(vector[j].u.score)) { int_convertion_error = 1; } } else if (byval->encoding == REDIS_ENCODING_INT) { /* Don't need to decode the object if it's * integer-encoded (the only encoding supported) so * far. We can just cast it */ vector[j].u.score = (long)byval->ptr; } else { redisAssertWithInfo(c,sortval,1 != 1); } } /* when the object was retrieved using lookupKeyByPattern, * its refcount needs to be decreased. */ if (sortby) { decrRefCount(byval); } } } /* We are ready to sort the vector... perform a bit of sanity check * on the LIMIT option too. We'll use a partial version of quicksort. */ start = (limit_start < 0) ? 0 : limit_start; end = (limit_count < 0) ? vectorlen-1 : start+limit_count-1; if (start >= vectorlen) { start = vectorlen-1; end = vectorlen-2; } if (end >= vectorlen) end = vectorlen-1; server.sort_dontsort = dontsort; if (dontsort == 0) { server.sort_desc = desc; server.sort_alpha = alpha; server.sort_bypattern = sortby ? 1 : 0; if (sortby && (start != 0 || end != vectorlen-1)) pqsort(vector,vectorlen,sizeof(redisSortObject),sortCompare, start,end); else qsort(vector,vectorlen,sizeof(redisSortObject),sortCompare); } /* Send command output to the output buffer, performing the specified * GET/DEL/INCR/DECR operations if any. */ outputlen = getop ? getop*(end-start+1) : end-start+1; if (int_convertion_error) { addReplyError(c,"One or more scores can't be converted into double"); } else if (storekey == NULL) { /* STORE option not specified, sent the sorting result to client */ addReplyMultiBulkLen(c,outputlen); for (j = start; j <= end; j++) { listNode *ln; listIter li; if (!getop) addReplyBulk(c,vector[j].obj); listRewind(operations,&li); while((ln = listNext(&li))) { redisSortOperation *sop = ln->value; robj *val = lookupKeyByPattern(c->db,sop->pattern, vector[j].obj); if (sop->type == REDIS_SORT_GET) { if (!val) { addReply(c,shared.nullbulk); } else { addReplyBulk(c,val); decrRefCount(val); } } else { /* Always fails */ redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET); } } } } else { robj *sobj = createZiplistObject(); /* STORE option specified, set the sorting result as a List object */ for (j = start; j <= end; j++) { listNode *ln; listIter li; if (!getop) { listTypePush(sobj,vector[j].obj,REDIS_TAIL); } else { listRewind(operations,&li); while((ln = listNext(&li))) { redisSortOperation *sop = ln->value; robj *val = lookupKeyByPattern(c->db,sop->pattern, vector[j].obj); if (sop->type == REDIS_SORT_GET) { if (!val) val = createStringObject("",0); /* listTypePush does an incrRefCount, so we should take care * care of the incremented refcount caused by either * lookupKeyByPattern or createStringObject("",0) */ listTypePush(sobj,val,REDIS_TAIL); decrRefCount(val); } else { /* Always fails */ redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET); } } } } if (outputlen) { setKey(c->db,storekey,sobj); server.dirty += outputlen; } else if (dbDelete(c->db,storekey)) { signalModifiedKey(c->db,storekey); server.dirty++; } decrRefCount(sobj); addReplyLongLong(c,outputlen); } /* Cleanup */ if (sortval->type == REDIS_LIST || sortval->type == REDIS_SET) for (j = 0; j < vectorlen; j++) decrRefCount(vector[j].obj); decrRefCount(sortval); listRelease(operations); for (j = 0; j < vectorlen; j++) { if (alpha && vector[j].u.cmpobj) decrRefCount(vector[j].u.cmpobj); } zfree(vector); }
/* The SORT command is the most complex command in Redis. Warning: this code * is optimized for speed and a bit less for readability */ void sortCommand(redisClient *c) { list *operations; unsigned int outputlen = 0; int desc = 0, alpha = 0; long limit_start = 0, limit_count = -1, start, end; int j, dontsort = 0, vectorlen; int getop = 0; /* GET operation counter */ int int_convertion_error = 0; robj *sortval, *sortby = NULL, *storekey = NULL; redisSortObject *vector; /* Resulting vector to sort */ /* Lookup the key to sort. It must be of the right types */ sortval = lookupKeyRead(c->db,c->argv[1]); if (sortval && sortval->type != REDIS_SET && sortval->type != REDIS_LIST && sortval->type != REDIS_ZSET) { addReply(c,shared.wrongtypeerr); return; } /* Create a list of operations to perform for every sorted element. * Operations can be GET/DEL/INCR/DECR */ operations = listCreate(); listSetFreeMethod(operations,zfree); j = 2; /* options start at argv[2] */ /* Now we need to protect sortval incrementing its count, in the future * SORT may have options able to overwrite/delete keys during the sorting * and the sorted key itself may get destroyed */ if (sortval) incrRefCount(sortval); else sortval = createListObject(); /* The SORT command has an SQL-alike syntax, parse it */ while(j < c->argc) { int leftargs = c->argc-j-1; if (!strcasecmp(c->argv[j]->ptr,"asc")) { desc = 0; } else if (!strcasecmp(c->argv[j]->ptr,"desc")) { desc = 1; } else if (!strcasecmp(c->argv[j]->ptr,"alpha")) { alpha = 1; } else if (!strcasecmp(c->argv[j]->ptr,"limit") && leftargs >= 2) { if ((getLongFromObjectOrReply(c, c->argv[j+1], &limit_start, NULL) != REDIS_OK) || (getLongFromObjectOrReply(c, c->argv[j+2], &limit_count, NULL) != REDIS_OK)) return; j+=2; } else if (!strcasecmp(c->argv[j]->ptr,"store") && leftargs >= 1) { storekey = c->argv[j+1]; j++; } else if (!strcasecmp(c->argv[j]->ptr,"by") && leftargs >= 1) { sortby = c->argv[j+1]; /* If the BY pattern does not contain '*', i.e. it is constant, * we don't need to sort nor to lookup the weight keys. */ if (strchr(c->argv[j+1]->ptr,'*') == NULL) dontsort = 1; j++; } else if (!strcasecmp(c->argv[j]->ptr,"get") && leftargs >= 1) { listAddNodeTail(operations,createSortOperation( REDIS_SORT_GET,c->argv[j+1])); getop++; j++; } else { decrRefCount(sortval); listRelease(operations); addReply(c,shared.syntaxerr); return; } j++; } /* For the STORE option, or when SORT is called from a Lua script, * we want to force a specific ordering even when no explicit ordering * was asked (SORT BY nosort). This guarantees that replication / AOF * is deterministic. * * However in the case 'dontsort' is true, but the type to sort is a * sorted set, we don't need to do anything as ordering is guaranteed * in this special case. */ if ((storekey || c->flags & REDIS_LUA_CLIENT) && (dontsort && sortval->type != REDIS_ZSET)) { /* Force ALPHA sorting */ dontsort = 0; alpha = 1; sortby = NULL; } /* Destructively convert encoded sorted sets for SORT. */ if (sortval->type == REDIS_ZSET) zsetConvert(sortval, REDIS_ENCODING_SKIPLIST); /* Objtain the length of the object to sort. */ switch(sortval->type) { case REDIS_LIST: vectorlen = listTypeLength(sortval); break; case REDIS_SET: vectorlen = setTypeSize(sortval); break; case REDIS_ZSET: vectorlen = dictSize(((zset*)sortval->ptr)->dict); break; default: vectorlen = 0; redisPanic("Bad SORT type"); /* Avoid GCC warning */ } /* Perform LIMIT start,count sanity checking. */ start = (limit_start < 0) ? 0 : limit_start; end = (limit_count < 0) ? vectorlen-1 : start+limit_count-1; if (start >= vectorlen) { start = vectorlen-1; end = vectorlen-2; } if (end >= vectorlen) end = vectorlen-1; /* Optimization: * * 1) if the object to sort is a sorted set. * 2) There is nothing to sort as dontsort is true (BY <constant string>). * 3) We have a LIMIT option that actually reduces the number of elements * to fetch. * * In this case to load all the objects in the vector is a huge waste of * resources. We just allocate a vector that is big enough for the selected * range length, and make sure to load just this part in the vector. */ if (sortval->type == REDIS_ZSET && dontsort && (start != 0 || end != vectorlen-1)) { vectorlen = end-start+1; } /* Load the sorting vector with all the objects to sort */ vector = zmalloc(sizeof(redisSortObject)*vectorlen); j = 0; if (sortval->type == REDIS_LIST) { listTypeIterator *li = listTypeInitIterator(sortval,0,REDIS_TAIL); listTypeEntry entry; while(listTypeNext(li,&entry)) { vector[j].obj = listTypeGet(&entry); vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; } listTypeReleaseIterator(li); } else if (sortval->type == REDIS_SET) { setTypeIterator *si = setTypeInitIterator(sortval); robj *ele; while((ele = setTypeNextObject(si)) != NULL) { vector[j].obj = ele; vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; } setTypeReleaseIterator(si); } else if (sortval->type == REDIS_ZSET && dontsort) { /* Special handling for a sorted set, if 'dontsort' is true. * This makes sure we return elements in the sorted set original * ordering, accordingly to DESC / ASC options. * * Note that in this case we also handle LIMIT here in a direct * way, just getting the required range, as an optimization. */ zset *zs = sortval->ptr; zskiplist *zsl = zs->zsl; zskiplistNode *ln; robj *ele; int rangelen = vectorlen; /* Check if starting point is trivial, before doing log(N) lookup. */ if (desc) { long zsetlen = dictSize(((zset*)sortval->ptr)->dict); ln = zsl->tail; if (start > 0) ln = zslGetElementByRank(zsl,zsetlen-start); } else { ln = zsl->header->level[0].forward; if (start > 0) ln = zslGetElementByRank(zsl,start+1); } while(rangelen--) { redisAssertWithInfo(c,sortval,ln != NULL); ele = ln->obj; vector[j].obj = ele; vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; ln = desc ? ln->backward : ln->level[0].forward; } /* The code producing the output does not know that in the case of * sorted set, 'dontsort', and LIMIT, we are able to get just the * range, already sorted, so we need to adjust "start" and "end" * to make sure start is set to 0. */ end -= start; start = 0; } else if (sortval->type == REDIS_ZSET) { dict *set = ((zset*)sortval->ptr)->dict; dictIterator *di; dictEntry *setele; di = dictGetIterator(set); while((setele = dictNext(di)) != NULL) { vector[j].obj = dictGetKey(setele); vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; } dictReleaseIterator(di); } else { redisPanic("Unknown type"); } redisAssertWithInfo(c,sortval,j == vectorlen); /* Now it's time to load the right scores in the sorting vector */ if (dontsort == 0) { for (j = 0; j < vectorlen; j++) { robj *byval; if (sortby) { /* lookup value to sort by */ byval = lookupKeyByPattern(c->db,sortby,vector[j].obj); if (!byval) continue; } else { /* use object itself to sort by */ byval = vector[j].obj; } if (alpha) { if (sortby) vector[j].u.cmpobj = getDecodedObject(byval); } else { if (byval->encoding == REDIS_ENCODING_RAW) { char *eptr; vector[j].u.score = strtod(byval->ptr,&eptr); if (eptr[0] != '\0' || errno == ERANGE || isnan(vector[j].u.score)) { int_convertion_error = 1; } } else if (byval->encoding == REDIS_ENCODING_INT) { /* Don't need to decode the object if it's * integer-encoded (the only encoding supported) so * far. We can just cast it */ vector[j].u.score = (long)byval->ptr; } else { redisAssertWithInfo(c,sortval,1 != 1); } } /* when the object was retrieved using lookupKeyByPattern, * its refcount needs to be decreased. */ if (sortby) { decrRefCount(byval); } } } if (dontsort == 0) { server.sort_desc = desc; server.sort_alpha = alpha; server.sort_bypattern = sortby ? 1 : 0; server.sort_store = storekey ? 1 : 0; if (sortby && (start != 0 || end != vectorlen-1)) pqsort(vector,vectorlen,sizeof(redisSortObject),sortCompare, start,end); else qsort(vector,vectorlen,sizeof(redisSortObject),sortCompare); } /* Send command output to the output buffer, performing the specified * GET/DEL/INCR/DECR operations if any. */ outputlen = getop ? getop*(end-start+1) : end-start+1; if (int_convertion_error) { addReplyError(c,"One or more scores can't be converted into double"); } else if (storekey == NULL) { /* STORE option not specified, sent the sorting result to client */ addReplyMultiBulkLen(c,outputlen); for (j = start; j <= end; j++) { listNode *ln; listIter li; if (!getop) addReplyBulk(c,vector[j].obj); listRewind(operations,&li); while((ln = listNext(&li))) { redisSortOperation *sop = ln->value; robj *val = lookupKeyByPattern(c->db,sop->pattern, vector[j].obj); if (sop->type == REDIS_SORT_GET) { if (!val) { addReply(c,shared.nullbulk); } else { addReplyBulk(c,val); decrRefCount(val); } } else { /* Always fails */ redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET); } } } } else { robj *sobj = createZiplistObject(); /* STORE option specified, set the sorting result as a List object */ for (j = start; j <= end; j++) { listNode *ln; listIter li; if (!getop) { listTypePush(sobj,vector[j].obj,REDIS_TAIL); } else { listRewind(operations,&li); while((ln = listNext(&li))) { redisSortOperation *sop = ln->value; robj *val = lookupKeyByPattern(c->db,sop->pattern, vector[j].obj); if (sop->type == REDIS_SORT_GET) { if (!val) val = createStringObject("",0); /* listTypePush does an incrRefCount, so we should take care * care of the incremented refcount caused by either * lookupKeyByPattern or createStringObject("",0) */ listTypePush(sobj,val,REDIS_TAIL); decrRefCount(val); } else { /* Always fails */ redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET); } } } } if (outputlen) { setKey(c->db,storekey,sobj); notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"sortstore",storekey, c->db->id); server.dirty += outputlen; } else if (dbDelete(c->db,storekey)) { signalModifiedKey(c->db,storekey); notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",storekey,c->db->id); server.dirty++; } decrRefCount(sobj); addReplyLongLong(c,outputlen); } /* Cleanup */ if (sortval->type == REDIS_LIST || sortval->type == REDIS_SET) for (j = 0; j < vectorlen; j++) decrRefCount(vector[j].obj); decrRefCount(sortval); listRelease(operations); for (j = 0; j < vectorlen; j++) { if (alpha && vector[j].u.cmpobj) decrRefCount(vector[j].u.cmpobj); } zfree(vector); }
void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *dstkey, int op) { robj **sets = zmalloc(sizeof(robj*)*setnum); setTypeIterator *si; robj *ele, *dstset = NULL; int j, cardinality = 0; int diff_algo = 1; for (j = 0; j < setnum; j++) { robj *setobj = dstkey ? lookupKeyWrite(c->db,setkeys[j]) : lookupKeyRead(c->db,setkeys[j]); if (!setobj) { sets[j] = NULL; continue; } if (checkType(c,setobj,REDIS_SET)) { zfree(sets); return; } sets[j] = setobj; } /* Select what DIFF algorithm to use. * * Algorithm 1 is O(N*M) where N is the size of the element first set * and M the total number of sets. * * Algorithm 2 is O(N) where N is the total number of elements in all * the sets. * * We compute what is the best bet with the current input here. */ if (op == REDIS_OP_DIFF && sets[0]) { long long algo_one_work = 0, algo_two_work = 0; for (j = 0; j < setnum; j++) { if (sets[j] == NULL) continue; algo_one_work += setTypeSize(sets[0]); algo_two_work += setTypeSize(sets[j]); } /* Algorithm 1 has better constant times and performs less operations * if there are elements in common. Give it some advantage. */ algo_one_work /= 2; diff_algo = (algo_one_work <= algo_two_work) ? 1 : 2; if (diff_algo == 1 && setnum > 1) { /* With algorithm 1 it is better to order the sets to subtract * by decreasing size, so that we are more likely to find * duplicated elements ASAP. */ qsort(sets+1,setnum-1,sizeof(robj*), qsortCompareSetsByRevCardinality); } } /* We need a temp set object to store our union. If the dstkey * is not NULL (that is, we are inside an SUNIONSTORE operation) then * this set object will be the resulting object to set into the target key*/ dstset = createIntsetObject(); if (op == REDIS_OP_UNION) { /* Union is trivial, just add every element of every set to the * temporary set. */ for (j = 0; j < setnum; j++) { if (!sets[j]) continue; /* non existing keys are like empty sets */ si = setTypeInitIterator(sets[j]); while((ele = setTypeNextObject(si)) != NULL) { if (setTypeAdd(dstset,ele)) cardinality++; decrRefCount(ele); } setTypeReleaseIterator(si); } } else if (op == REDIS_OP_DIFF && sets[0] && diff_algo == 1) { /* DIFF Algorithm 1: * * We perform the diff by iterating all the elements of the first set, * and only adding it to the target set if the element does not exist * into all the other sets. * * This way we perform at max N*M operations, where N is the size of * the first set, and M the number of sets. */ si = setTypeInitIterator(sets[0]); while((ele = setTypeNextObject(si)) != NULL) { for (j = 1; j < setnum; j++) { if (!sets[j]) continue; /* no key is an empty set. */ if (sets[j] == sets[0]) break; /* same set! */ if (setTypeIsMember(sets[j],ele)) break; } if (j == setnum) { /* There is no other set with this element. Add it. */ setTypeAdd(dstset,ele); cardinality++; } decrRefCount(ele); } setTypeReleaseIterator(si); } else if (op == REDIS_OP_DIFF && sets[0] && diff_algo == 2) { /* DIFF Algorithm 2: * * Add all the elements of the first set to the auxiliary set. * Then remove all the elements of all the next sets from it. * * This is O(N) where N is the sum of all the elements in every * set. */ for (j = 0; j < setnum; j++) { if (!sets[j]) continue; /* non existing keys are like empty sets */ si = setTypeInitIterator(sets[j]); while((ele = setTypeNextObject(si)) != NULL) { if (j == 0) { if (setTypeAdd(dstset,ele)) cardinality++; } else { if (setTypeRemove(dstset,ele)) cardinality--; } decrRefCount(ele); } setTypeReleaseIterator(si); /* Exit if result set is empty as any additional removal * of elements will have no effect. */ if (cardinality == 0) break; } } /* Output the content of the resulting set, if not in STORE mode */ if (!dstkey) { addReplyMultiBulkLen(c,cardinality); si = setTypeInitIterator(dstset); while((ele = setTypeNextObject(si)) != NULL) { addReplyBulk(c,ele); decrRefCount(ele); } setTypeReleaseIterator(si); decrRefCount(dstset); } else { /* If we have a target key where to store the resulting set * create this key with the result set inside */ int deleted = dbDelete(c->db,dstkey); if (setTypeSize(dstset) > 0) { dbAdd(c->db,dstkey,dstset); addReplyLongLong(c,setTypeSize(dstset)); notifyKeyspaceEvent(REDIS_NOTIFY_SET, op == REDIS_OP_UNION ? "sunionstore" : "sdiffstore", dstkey,c->db->id); } else { decrRefCount(dstset); addReply(c,shared.czero); if (deleted) notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del", dstkey,c->db->id); } signalModifiedKey(c->db,dstkey); server.dirty++; } zfree(sets); }
/* Return the value associated to the key with a name obtained using * the following rules: * * 1) The first occurrence of '*' in 'pattern' is substituted with 'subst'. * * 2) If 'pattern' matches the "->" string, everything on the left of * the arrow is treated as the name of a hash field, and the part on the * left as the key name containing a hash. The value of the specified * field is returned. * * 3) If 'pattern' equals "#", the function simply returns 'subst' itself so * that the SORT command can be used like: SORT key GET # to retrieve * the Set/List elements directly. * * The returned object will always have its refcount increased by 1 * when it is non-NULL. */ robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) { char *p, *f, *k; sds spat, ssub; robj *keyobj, *fieldobj = NULL, *o; int prefixlen, sublen, postfixlen, fieldlen; /* If the pattern is "#" return the substitution object itself in order * to implement the "SORT ... GET #" feature. */ spat = pattern->ptr; if (spat[0] == '#' && spat[1] == '\0') { incrRefCount(subst); return subst; } /* The substitution object may be specially encoded. If so we create * a decoded object on the fly. Otherwise getDecodedObject will just * increment the ref count, that we'll decrement later. */ subst = getDecodedObject(subst); ssub = subst->ptr; /* If we can't find '*' in the pattern we return NULL as to GET a * fixed key does not make sense. */ p = strchr(spat,'*'); if (!p) { decrRefCount(subst); return NULL; } /* Find out if we're dealing with a hash dereference. */ if ((f = strstr(p+1, "->")) != NULL && *(f+2) != '\0') { fieldlen = sdslen(spat)-(f-spat)-2; fieldobj = createStringObject(f+2,fieldlen); } else { fieldlen = 0; } /* Perform the '*' substitution. */ prefixlen = p-spat; sublen = sdslen(ssub); postfixlen = sdslen(spat)-(prefixlen+1)-(fieldlen ? fieldlen+2 : 0); keyobj = createStringObject(NULL,prefixlen+sublen+postfixlen); k = keyobj->ptr; memcpy(k,spat,prefixlen); memcpy(k+prefixlen,ssub,sublen); memcpy(k+prefixlen+sublen,p+1,postfixlen); decrRefCount(subst); /* Incremented by decodeObject() */ /* Lookup substituted key */ o = lookupKeyRead(db,keyobj); if (o == NULL) goto noobj; if (fieldobj) { if (o->type != REDIS_HASH) goto noobj; /* Retrieve value from hash by the field name. This operation * already increases the refcount of the returned object. */ o = hashTypeGetObject(o, fieldobj); } else { if (o->type != REDIS_STRING) goto noobj; /* Every object that this function returns needs to have its refcount * increased. sortCommand decreases it again. */ incrRefCount(o); } decrRefCount(keyobj); if (fieldobj) decrRefCount(fieldobj); return o; noobj: decrRefCount(keyobj); if (fieldlen) decrRefCount(fieldobj); return NULL; }
/* This command implements both ZRANGEBYSCORE and ZCOUNT. * If justcount is non-zero, just the count is returned. */ void genericZrangebyscoreCommand(redisClient *c, int justcount) { robj *o; double min, max; int minex = 0, maxex = 0; /* are min or max exclusive? */ int offset = 0, limit = -1; int withscores = 0; int badsyntax = 0; /* Parse the min-max interval. If one of the values is prefixed * by the "(" character, it's considered "open". For instance * ZRANGEBYSCORE zset (1.5 (2.5 will match min < x < max * ZRANGEBYSCORE zset 1.5 2.5 will instead match min <= x <= max */ if (((char*)c->argv[2]->ptr)[0] == '(') { min = strtod((char*)c->argv[2]->ptr+1,NULL); minex = 1; } else { min = strtod(c->argv[2]->ptr,NULL); } if (((char*)c->argv[3]->ptr)[0] == '(') { max = strtod((char*)c->argv[3]->ptr+1,NULL); maxex = 1; } else { max = strtod(c->argv[3]->ptr,NULL); } /* Parse "WITHSCORES": note that if the command was called with * the name ZCOUNT then we are sure that c->argc == 4, so we'll never * enter the following paths to parse WITHSCORES and LIMIT. */ if (c->argc == 5 || c->argc == 8) { if (strcasecmp(c->argv[c->argc-1]->ptr,"withscores") == 0) withscores = 1; else badsyntax = 1; } if (c->argc != (4 + withscores) && c->argc != (7 + withscores)) badsyntax = 1; if (badsyntax) { addReplySds(c, sdsnew("-ERR wrong number of arguments for ZRANGEBYSCORE\r\n")); return; } /* Parse "LIMIT" */ if (c->argc == (7 + withscores) && strcasecmp(c->argv[4]->ptr,"limit")) { addReply(c,shared.syntaxerr); return; } else if (c->argc == (7 + withscores)) { offset = atoi(c->argv[5]->ptr); limit = atoi(c->argv[6]->ptr); if (offset < 0) offset = 0; } /* Ok, lookup the key and get the range */ o = lookupKeyRead(c->db,c->argv[1]); if (o == NULL) { addReply(c,justcount ? shared.czero : shared.emptymultibulk); } else { if (o->type != REDIS_ZSET) { addReply(c,shared.wrongtypeerr); } else { zset *zsetobj = o->ptr; zskiplist *zsl = zsetobj->zsl; zskiplistNode *ln; robj *ele, *lenobj = NULL; unsigned long rangelen = 0; /* Get the first node with the score >= min, or with * score > min if 'minex' is true. */ ln = zslFirstWithScore(zsl,min); while (minex && ln && ln->score == min) ln = ln->forward[0]; if (ln == NULL) { /* No element matching the speciifed interval */ addReply(c,justcount ? shared.czero : shared.emptymultibulk); return; } /* We don't know in advance how many matching elements there * are in the list, so we push this object that will represent * the multi-bulk length in the output buffer, and will "fix" * it later */ if (!justcount) { lenobj = createObject(REDIS_STRING,NULL); addReply(c,lenobj); decrRefCount(lenobj); } while(ln && (maxex ? (ln->score < max) : (ln->score <= max))) { if (offset) { offset--; ln = ln->forward[0]; continue; } if (limit == 0) break; if (!justcount) { ele = ln->obj; addReplyBulk(c,ele); if (withscores) addReplyDouble(c,ln->score); } ln = ln->forward[0]; rangelen++; if (limit > 0) limit--; } if (justcount) { addReplyLongLong(c,(long)rangelen); } else { lenobj->ptr = sdscatprintf(sdsempty(),"*%lu\r\n", withscores ? (rangelen*2) : rangelen); } } } }