1 Star 2 Fork 2

mirrorgit / phpredis

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
redis_commands.c 151.56 KB
一键复制 编辑 原始数据 按行查看 历史
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852
/* -*- Mode: C; tab-width: 4 -*- */
/*
+----------------------------------------------------------------------+
| Copyright (c) 1997-2009 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Original Author: Michael Grunder <michael.grunder@gmail.com |
| Maintainer: Nicolas Favre-Felix <n.favre-felix@owlient.eu> |
+----------------------------------------------------------------------+
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "redis_commands.h"
#include "php_network.h"
#ifndef PHP_WIN32
#include <netinet/tcp.h> /* TCP_KEEPALIVE */
#else
#include <winsock.h>
#endif
#include <zend_exceptions.h>
/* Georadius sort type */
typedef enum geoSortType {
SORT_NONE,
SORT_ASC,
SORT_DESC
} geoSortType;
/* Georadius store type */
typedef enum geoStoreType {
STORE_NONE,
STORE_COORD,
STORE_DIST
} geoStoreType;
/* Georadius options structure */
typedef struct geoOptions {
int withcoord;
int withdist;
int withhash;
long count;
geoSortType sort;
geoStoreType store;
zend_string *key;
} geoOptions;
/* Local passthrough macro for command construction. Given that these methods
* are generic (so they work whether the caller is Redis or RedisCluster) we
* will always have redis_sock, slot*, and */
#define REDIS_CMD_SPPRINTF(ret, kw, fmt, ...) \
redis_spprintf(redis_sock, slot, ret, kw, fmt, ##__VA_ARGS__)
/* Generic commands based on method signature and what kind of things we're
* processing. Lots of Redis commands take something like key, value, or
* key, value long. Each unique signature like this is written only once */
/* A command that takes no arguments */
int redis_empty_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "");
return SUCCESS;
}
/* Helper to construct a raw command. Given that the cluster and non cluster
* versions are different (RedisCluster needs an additional argument to direct
* the command) we take the start of our array and count */
int redis_build_raw_cmd(zval *z_args, int argc, char **cmd, int *cmd_len)
{
smart_string cmdstr = {0};
int i;
/* Make sure our first argument is a string */
if (Z_TYPE(z_args[0]) != IS_STRING) {
php_error_docref(NULL, E_WARNING,
"When sending a 'raw' command, the first argument must be a string!");
return FAILURE;
}
/* Initialize our command string */
redis_cmd_init_sstr(&cmdstr, argc-1, Z_STRVAL(z_args[0]), Z_STRLEN(z_args[0]));
for (i = 1; i < argc; i++) {
switch (Z_TYPE(z_args[i])) {
case IS_STRING:
redis_cmd_append_sstr(&cmdstr, Z_STRVAL(z_args[i]),
Z_STRLEN(z_args[i]));
break;
case IS_LONG:
redis_cmd_append_sstr_long(&cmdstr,Z_LVAL(z_args[i]));
break;
case IS_DOUBLE:
redis_cmd_append_sstr_dbl(&cmdstr,Z_DVAL(z_args[i]));
break;
default:
php_error_docref(NULL, E_WARNING,
"Raw command arguments must be scalar values!");
efree(cmdstr.c);
return FAILURE;
}
}
/* Push command and length to caller */
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
smart_string *
redis_build_script_cmd(smart_string *cmd, int argc, zval *z_args)
{
int i;
zend_string *zstr;
if (Z_TYPE(z_args[0]) != IS_STRING) {
return NULL;
}
// Branch based on the directive
if (!strcasecmp(Z_STRVAL(z_args[0]), "flush") || !strcasecmp(Z_STRVAL(z_args[0]), "kill")) {
// Simple SCRIPT FLUSH, or SCRIPT_KILL command
REDIS_CMD_INIT_SSTR_STATIC(cmd, argc, "SCRIPT");
redis_cmd_append_sstr(cmd, Z_STRVAL(z_args[0]), Z_STRLEN(z_args[0]));
} else if (!strcasecmp(Z_STRVAL(z_args[0]), "load")) {
// Make sure we have a second argument, and it's not empty. If it is
// empty, we can just return an empty array (which is what Redis does)
if (argc < 2 || Z_TYPE(z_args[1]) != IS_STRING || Z_STRLEN(z_args[1]) < 1) {
return NULL;
}
// Format our SCRIPT LOAD command
REDIS_CMD_INIT_SSTR_STATIC(cmd, argc, "SCRIPT");
redis_cmd_append_sstr(cmd, "LOAD", sizeof("LOAD") - 1);
redis_cmd_append_sstr(cmd, Z_STRVAL(z_args[1]), Z_STRLEN(z_args[1]));
} else if (!strcasecmp(Z_STRVAL(z_args[0]), "exists")) {
// Make sure we have a second argument
if (argc < 2) {
return NULL;
}
/* Construct our SCRIPT EXISTS command */
REDIS_CMD_INIT_SSTR_STATIC(cmd, argc, "SCRIPT");
redis_cmd_append_sstr(cmd, "EXISTS", sizeof("EXISTS") - 1);
for (i = 1; i < argc; ++i) {
zstr = zval_get_string(&z_args[i]);
redis_cmd_append_sstr(cmd, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
zend_string_release(zstr);
}
} else {
/* Unknown directive */
return NULL;
}
return cmd;
}
/* Command that takes one optional string */
int redis_opt_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *arg = NULL;
size_t arglen;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s!", &arg, &arglen) == FAILURE) {
return FAILURE;
}
if (arg != NULL) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "s", arg, arglen);
} else {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "");
}
return SUCCESS;
}
/* Generic command where we just take a string and do nothing to it*/
int redis_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *arg;
size_t arg_len;
// Parse args
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &arg, &arg_len)
==FAILURE)
{
return FAILURE;
}
// Build the command without molesting the string
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "s", arg, arg_len);
return SUCCESS;
}
/* Key, long, zval (serialized) */
int redis_key_long_val_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *key = NULL;
size_t key_len;
zend_long expire;
zval *z_val;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "slz", &key, &key_len,
&expire, &z_val) == FAILURE)
{
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "klv", key, key_len, expire, z_val);
return SUCCESS;
}
/* Generic key, long, string (unserialized) */
int redis_key_long_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *key, *val;
size_t key_len, val_len;
zend_long lval;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sls", &key, &key_len,
&lval, &val, &val_len) == FAILURE)
{
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kds", key, key_len, (int)lval, val, val_len);
return SUCCESS;
}
/* Generic command construction when we just take a key and value */
int redis_kv_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *key;
size_t key_len;
zval *z_val;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz", &key, &key_len,
&z_val) == FAILURE)
{
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kv", key, key_len, z_val);
return SUCCESS;
}
/* Generic command that takes a key and an unserialized value */
int redis_key_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *key, *val;
size_t key_len, val_len;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &key, &key_len,
&val, &val_len) == FAILURE)
{
return FAILURE;
}
// Construct command
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ks", key, key_len, val, val_len);
return SUCCESS;
}
/* Key, string, string without serialization (ZCOUNT, ZREMRANGEBYSCORE) */
int redis_key_str_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *k, *v1, *v2;
size_t klen, v1len, v2len;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss", &k, &klen,
&v1, &v1len, &v2, &v2len) == FAILURE)
{
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kss", k, klen, v1, v1len, v2, v2len);
// Success!
return SUCCESS;
}
/* Generic command that takes two keys */
int redis_key_key_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *k1, *k2;
size_t k1len, k2len;
int k1free, k2free;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &k1, &k1len,
&k2, &k2len) == FAILURE)
{
return FAILURE;
}
// Prefix both keys
k1free = redis_key_prefix(redis_sock, &k1, &k1len);
k2free = redis_key_prefix(redis_sock, &k2, &k2len);
// If a slot is requested, we can test that they hash the same
if (slot) {
// Slots where these keys resolve
short slot1 = cluster_hash_key(k1, k1len);
short slot2 = cluster_hash_key(k2, k2len);
// Check if Redis would give us a CROSSLOT error
if (slot1 != slot2) {
php_error_docref(0, E_WARNING, "Keys don't hash to the same slot");
if (k1free) efree(k1);
if (k2free) efree(k2);
return FAILURE;
}
// They're both the same
*slot = slot1;
}
/* Send keys as normal strings because we manually prefixed to check against
* cross slot error. */
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ss", k1, k1len, k2, k2len);
/* Clean keys up if we prefixed */
if (k1free) efree(k1);
if (k2free) efree(k2);
return SUCCESS;
}
/* Generic command construction where we take a key and a long */
int redis_key_long_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *key;
size_t keylen;
zend_long lval;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sl", &key, &keylen, &lval)
==FAILURE)
{
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kl", key, keylen, lval);
// Success!
return SUCCESS;
}
/* long, long */
int redis_long_long_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
zend_long v1, v2;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll", &v1, &v2)
== FAILURE)
{
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ll", v1, v2);
return SUCCESS;
}
/* key, long, long */
int redis_key_long_long_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *key;
size_t key_len;
zend_long val1, val2;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sll", &key, &key_len,
&val1, &val2) == FAILURE)
{
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kll", key, key_len, val1, val2);
return SUCCESS;
}
/* Generic command where we take a single key */
int redis_key_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *key;
size_t key_len;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &key, &key_len)
==FAILURE)
{
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "k", key, key_len);
return SUCCESS;
}
int redis_flush_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot, void **ctx)
{
zend_bool async = 0;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &async) == FAILURE) {
return FAILURE;
}
if (async) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "s", "ASYNC", sizeof("ASYNC") - 1);
} else {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "");
}
return SUCCESS;
}
/* Generic command where we take a key and a double */
int redis_key_dbl_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *key;
size_t key_len;
double val;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sd", &key, &key_len,
&val) == FAILURE)
{
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kf", key, key_len, val);
return SUCCESS;
}
/* Generic to construct SCAN and variant commands */
int redis_fmt_scan_cmd(char **cmd, REDIS_SCAN_TYPE type, char *key, int key_len,
long it, char *pat, int pat_len, long count)
{
static char *kw[] = {"SCAN","SSCAN","HSCAN","ZSCAN"};
int argc;
smart_string cmdstr = {0};
// Figure out our argument count
argc = 1 + (type!=TYPE_SCAN) + (pat_len>0?2:0) + (count>0?2:0);
redis_cmd_init_sstr(&cmdstr, argc, kw[type], strlen(kw[type]));
// Append our key if it's not a regular SCAN command
if (type != TYPE_SCAN) {
redis_cmd_append_sstr(&cmdstr, key, key_len);
}
// Append cursor
redis_cmd_append_sstr_long(&cmdstr, it);
// Append count if we've got one
if (count) {
redis_cmd_append_sstr(&cmdstr,"COUNT",sizeof("COUNT")-1);
redis_cmd_append_sstr_long(&cmdstr, count);
}
// Append pattern if we've got one
if (pat_len) {
redis_cmd_append_sstr(&cmdstr,"MATCH",sizeof("MATCH")-1);
redis_cmd_append_sstr(&cmdstr,pat,pat_len);
}
// Push command to the caller, return length
*cmd = cmdstr.c;
return cmdstr.len;
}
/* ZRANGE/ZREVRANGE */
int redis_zrange_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, int *withscores,
short *slot, void **ctx)
{
char *key;
size_t key_len;
zend_long start, end;
zend_string *zkey;
zval *z_ws = NULL, *z_ele;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sll|z", &key, &key_len,
&start, &end, &z_ws) == FAILURE)
{
return FAILURE;
}
// Clear withscores arg
*withscores = 0;
/* Accept ['withscores' => true], or the legacy `true` value */
if (z_ws) {
if (Z_TYPE_P(z_ws) == IS_ARRAY) {
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(z_ws), zkey, z_ele) {
ZVAL_DEREF(z_ele);
if (zend_string_equals_literal_ci(zkey, "withscores")) {
*withscores = zval_is_true(z_ele);
break;
}
} ZEND_HASH_FOREACH_END();
} else if (Z_TYPE_P(z_ws) == IS_TRUE) {
*withscores = Z_TYPE_P(z_ws) == IS_TRUE;
}
}
if (*withscores) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kdds", key, key_len, start, end,
"WITHSCORES", sizeof("WITHSCORES") - 1);
} else {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kdd", key, key_len, start, end);
}
return SUCCESS;
}
int redis_zrangebyscore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, int *withscores,
short *slot, void **ctx)
{
char *key, *start, *end;
int has_limit = 0;
long offset, count;
size_t key_len, start_len, end_len;
zval *z_opt=NULL, *z_ele;
zend_string *zkey;
HashTable *ht_opt;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|a", &key, &key_len,
&start, &start_len, &end, &end_len, &z_opt)
==FAILURE)
{
return FAILURE;
}
// Check for an options array
if (z_opt && Z_TYPE_P(z_opt) == IS_ARRAY) {
ht_opt = Z_ARRVAL_P(z_opt);
ZEND_HASH_FOREACH_STR_KEY_VAL(ht_opt, zkey, z_ele) {
/* All options require a string key type */
if (!zkey) continue;
ZVAL_DEREF(z_ele);
/* Check for withscores and limit */
if (zend_string_equals_literal_ci(zkey, "withscores")) {
*withscores = zval_is_true(z_ele);
} else if (zend_string_equals_literal_ci(zkey, "limit") && Z_TYPE_P(z_ele) == IS_ARRAY) {
HashTable *htlimit = Z_ARRVAL_P(z_ele);
zval *zoff, *zcnt;
/* We need two arguments (offset and count) */
if ((zoff = zend_hash_index_find(htlimit, 0)) != NULL &&
(zcnt = zend_hash_index_find(htlimit, 1)) != NULL
) {
/* Set our limit if we can get valid longs from both args */
offset = zval_get_long(zoff);
count = zval_get_long(zcnt);
has_limit = 1;
}
}
} ZEND_HASH_FOREACH_END();
}
// Construct our command
if (*withscores) {
if (has_limit) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ksssdds", key, key_len,
start, start_len, end, end_len, "LIMIT", 5, offset, count,
"WITHSCORES", 10);
} else {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ksss", key, key_len, start,
start_len, end, end_len, "WITHSCORES", 10);
}
} else {
if (has_limit) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ksssdd", key, key_len, start,
start_len, end, end_len, "LIMIT", 5, offset, count);
} else {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kss", key, key_len, start,
start_len, end, end_len);
}
}
return SUCCESS;
}
int
redis_zrandmember_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key;
int count = 0;
size_t key_len;
smart_string cmdstr = {0};
zend_bool withscores = 0;
zval *z_opts = NULL, *z_ele;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|a",
&key, &key_len, &z_opts) == FAILURE)
{
return FAILURE;
}
if (z_opts && Z_TYPE_P(z_opts) == IS_ARRAY) {
zend_string *zkey;
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(z_opts), zkey, z_ele) {
ZVAL_DEREF(z_ele);
if (zend_string_equals_literal_ci(zkey, "count")) {
count = zval_get_long(z_ele);
} else if (zend_string_equals_literal_ci(zkey, "withscores")) {
withscores = zval_is_true(z_ele);
}
} ZEND_HASH_FOREACH_END();
}
REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, 1 + (count != 0) + withscores, "ZRANDMEMBER");
redis_cmd_append_sstr_key(&cmdstr, key, key_len, redis_sock, slot);
if (count != 0) {
redis_cmd_append_sstr_long(&cmdstr, count);
*ctx = PHPREDIS_CTX_PTR;
}
if (withscores) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "WITHSCORES");
*ctx = PHPREDIS_CTX_PTR + 1;
}
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
int
redis_zdiff_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
int numkeys;
smart_string cmdstr = {0};
zval *z_keys, *z_opts = NULL, *z_ele;
zend_bool withscores = 0;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "a|a",
&z_keys, &z_opts) == FAILURE)
{
return FAILURE;
}
if ((numkeys = zend_hash_num_elements(Z_ARRVAL_P(z_keys))) == 0) {
return FAILURE;
}
if (z_opts && Z_TYPE_P(z_opts) == IS_ARRAY) {
zend_string *zkey;
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(z_opts), zkey, z_ele) {
if (zkey != NULL) {
ZVAL_DEREF(z_ele);
if (zend_string_equals_literal_ci(zkey, "withscores")) {
withscores = zval_is_true(z_ele);
}
}
} ZEND_HASH_FOREACH_END();
}
REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, 1 + numkeys + withscores, "ZDIFF");
redis_cmd_append_sstr_long(&cmdstr, numkeys);
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z_keys), z_ele) {
ZVAL_DEREF(z_ele);
redis_cmd_append_sstr_zval(&cmdstr, z_ele, redis_sock);
} ZEND_HASH_FOREACH_END();
if (withscores) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "WITHSCORES");
*ctx = PHPREDIS_CTX_PTR;
}
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
static int redis_cmd_append_sstr_score(smart_string *dst, zval *score) {
zend_uchar type;
zend_long lval;
size_t cmdlen;
double dval;
/* Get current command length */
cmdlen = dst->len;
if (Z_TYPE_P(score) == IS_LONG) {
redis_cmd_append_sstr_long(dst, Z_LVAL_P(score));
} else if (Z_TYPE_P(score) == IS_DOUBLE) {
redis_cmd_append_sstr_dbl(dst, Z_DVAL_P(score));
} else if (Z_TYPE_P(score) == IS_STRING) {
type = is_numeric_string(Z_STRVAL_P(score), Z_STRLEN_P(score), &lval, &dval, 0);
if (type == IS_LONG) {
redis_cmd_append_sstr_long(dst, lval);
} else if (type == IS_DOUBLE) {
redis_cmd_append_sstr_dbl(dst, dval);
} else if (zend_string_equals_literal_ci(Z_STR_P(score), "-inf") ||
zend_string_equals_literal_ci(Z_STR_P(score), "+inf") ||
zend_string_equals_literal_ci(Z_STR_P(score), "inf"))
{
redis_cmd_append_sstr_zstr(dst, Z_STR_P(score));
}
}
/* Success if we appended something */
if (dst->len > cmdlen)
return SUCCESS;
/* Nothing appended, failure */
php_error_docref(NULL, E_WARNING, "Weights must be numeric or '-inf','inf','+inf'");
return FAILURE;
}
int
redis_zinterunion_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
int numkeys;
smart_string cmdstr = {0};
zval *z_keys, *z_weights = NULL, *z_opts = NULL, *z_ele;
zend_string *aggregate = NULL;
zend_bool withscores = 0;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "a|a!a",
&z_keys, &z_weights, &z_opts) == FAILURE)
{
return FAILURE;
}
if ((numkeys = zend_hash_num_elements(Z_ARRVAL_P(z_keys))) == 0) {
return FAILURE;
}
if (z_weights) {
if (zend_hash_num_elements(Z_ARRVAL_P(z_weights)) != numkeys) {
php_error_docref(NULL, E_WARNING,
"WEIGHTS and keys array should be the same size!");
return FAILURE;
}
}
if (z_opts && Z_TYPE_P(z_opts) == IS_ARRAY) {
zend_string *zkey;
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(z_opts), zkey, z_ele) {
if (zkey != NULL) {
ZVAL_DEREF(z_ele);
if (zend_string_equals_literal_ci(zkey, "aggregate")) {
aggregate = zval_get_string(z_ele);
if (!zend_string_equals_literal_ci(aggregate, "sum") &&
!zend_string_equals_literal_ci(aggregate, "min") &&
!zend_string_equals_literal_ci(aggregate, "max")
) {
php_error_docref(NULL, E_WARNING,
"Invalid AGGREGATE option provided!");
zend_string_release(aggregate);
return FAILURE;
}
} else if (zend_string_equals_literal_ci(zkey, "withscores")) {
withscores = zval_is_true(z_ele);
}
}
} ZEND_HASH_FOREACH_END();
}
redis_cmd_init_sstr(&cmdstr, 1 + numkeys + (z_weights ? 1 + numkeys : 0) + (aggregate ? 2 : 0) + withscores, kw, strlen(kw));
redis_cmd_append_sstr_long(&cmdstr, numkeys);
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z_keys), z_ele) {
ZVAL_DEREF(z_ele);
redis_cmd_append_sstr_zval(&cmdstr, z_ele, redis_sock);
} ZEND_HASH_FOREACH_END();
if (z_weights) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "WEIGHTS");
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z_weights), z_ele) {
ZVAL_DEREF(z_ele);
if (redis_cmd_append_sstr_score(&cmdstr, z_ele) == FAILURE) {
if (aggregate) zend_string_release(aggregate);
efree(cmdstr.c);
return FAILURE;
}
} ZEND_HASH_FOREACH_END();
}
if (aggregate) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "AGGREGATE");
redis_cmd_append_sstr_zstr(&cmdstr, aggregate);
zend_string_release(aggregate);
}
if (withscores) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "WITHSCORES");
*ctx = PHPREDIS_CTX_PTR;
}
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
int
redis_zdiffstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *dst;
size_t dst_len;
int numkeys;
zval *z_keys, *z_ele;
smart_string cmdstr = {0};
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa",
&dst, &dst_len, &z_keys) == FAILURE)
{
return FAILURE;
}
if ((numkeys = zend_hash_num_elements(Z_ARRVAL_P(z_keys))) == 0) {
return FAILURE;
}
REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, 2 + numkeys, "ZDIFFSTORE");
redis_cmd_append_sstr(&cmdstr, dst, dst_len);
redis_cmd_append_sstr_long(&cmdstr, numkeys);
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z_keys), z_ele) {
ZVAL_DEREF(z_ele);
redis_cmd_append_sstr_zval(&cmdstr, z_ele, redis_sock);
} ZEND_HASH_FOREACH_END();
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
/* ZUNIONSTORE, ZINTERSTORE */
int
redis_zinterunionstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *key, *agg_op=NULL;
int key_free, argc = 2, keys_count;
size_t key_len, agg_op_len = 0;
zval *z_keys, *z_weights=NULL, *z_ele;
HashTable *ht_keys, *ht_weights=NULL;
smart_string cmdstr = {0};
// Parse args
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa|a!s", &key,
&key_len, &z_keys, &z_weights, &agg_op,
&agg_op_len) == FAILURE)
{
return FAILURE;
}
// Grab our keys
ht_keys = Z_ARRVAL_P(z_keys);
// Nothing to do if there aren't any
if ((keys_count = zend_hash_num_elements(ht_keys)) == 0) {
return FAILURE;
} else {
argc += keys_count;
}
// Handle WEIGHTS
if (z_weights != NULL) {
ht_weights = Z_ARRVAL_P(z_weights);
if (zend_hash_num_elements(ht_weights) != keys_count) {
php_error_docref(NULL, E_WARNING,
"WEIGHTS and keys array should be the same size!");
return FAILURE;
}
// "WEIGHTS" + key count
argc += keys_count + 1;
}
// AGGREGATE option
if (agg_op_len != 0) {
if (strncasecmp(agg_op, "SUM", sizeof("SUM")) &&
strncasecmp(agg_op, "MIN", sizeof("MIN")) &&
strncasecmp(agg_op, "MAX", sizeof("MAX")))
{
php_error_docref(NULL, E_WARNING,
"Invalid AGGREGATE option provided!");
return FAILURE;
}
// "AGGREGATE" + type
argc += 2;
}
// Prefix key
key_free = redis_key_prefix(redis_sock, &key, &key_len);
// Start building our command
redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw));
redis_cmd_append_sstr(&cmdstr, key, key_len);
redis_cmd_append_sstr_int(&cmdstr, keys_count);
// Set our slot, free the key if we prefixed it
CMD_SET_SLOT(slot,key,key_len);
if (key_free) efree(key);
// Process input keys
ZEND_HASH_FOREACH_VAL(ht_keys, z_ele) {
zend_string *zstr = zval_get_string(z_ele);
char *key = ZSTR_VAL(zstr);
size_t key_len = ZSTR_LEN(zstr);
// Prefix key if necissary
int key_free = redis_key_prefix(redis_sock, &key, &key_len);
// If we're in Cluster mode, verify the slot is the same
if (slot && *slot != cluster_hash_key(key,key_len)) {
php_error_docref(NULL, E_WARNING,
"All keys don't hash to the same slot!");
efree(cmdstr.c);
zend_string_release(zstr);
if (key_free) efree(key);
return FAILURE;
}
// Append this input set
redis_cmd_append_sstr(&cmdstr, key, key_len);
// Cleanup
zend_string_release(zstr);
if (key_free) efree(key);
} ZEND_HASH_FOREACH_END();
// Weights
if (ht_weights != NULL) {
redis_cmd_append_sstr(&cmdstr, "WEIGHTS", sizeof("WEIGHTS")-1);
// Process our weights
ZEND_HASH_FOREACH_VAL(ht_weights, z_ele) {
ZVAL_DEREF(z_ele);
if (redis_cmd_append_sstr_score(&cmdstr, z_ele) == FAILURE) {
efree(cmdstr.c);
return FAILURE;
}
} ZEND_HASH_FOREACH_END();
}
// AGGREGATE
if (agg_op_len != 0) {
redis_cmd_append_sstr(&cmdstr, "AGGREGATE", sizeof("AGGREGATE")-1);
redis_cmd_append_sstr(&cmdstr, agg_op, agg_op_len);
}
// Push out values
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
/* SUBSCRIBE/PSUBSCRIBE */
int redis_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
zval *z_arr, *z_chan;
HashTable *ht_chan;
smart_string cmdstr = {0};
subscribeContext *sctx = ecalloc(1, sizeof(*sctx));
size_t key_len;
int key_free;
char *key;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "af", &z_arr,
&(sctx->cb), &(sctx->cb_cache)) == FAILURE)
{
efree(sctx);
return FAILURE;
}
ht_chan = Z_ARRVAL_P(z_arr);
sctx->kw = kw;
sctx->argc = zend_hash_num_elements(ht_chan);
if (sctx->argc == 0) {
efree(sctx);
return FAILURE;
}
// Start command construction
redis_cmd_init_sstr(&cmdstr, sctx->argc, kw, strlen(kw));
// Iterate over channels
ZEND_HASH_FOREACH_VAL(ht_chan, z_chan) {
// We want to deal with strings here
zend_string *zstr = zval_get_string(z_chan);
// Grab channel name, prefix if required
key = ZSTR_VAL(zstr);
key_len = ZSTR_LEN(zstr);
key_free = redis_key_prefix(redis_sock, &key, &key_len);
// Add this channel
redis_cmd_append_sstr(&cmdstr, key, key_len);
zend_string_release(zstr);
// Free our key if it was prefixed
if (key_free) efree(key);
} ZEND_HASH_FOREACH_END();
// Push values out
*cmd_len = cmdstr.len;
*cmd = cmdstr.c;
*ctx = (void*)sctx;
// Pick a slot at random
CMD_RAND_SLOT(slot);
return SUCCESS;
}
/* UNSUBSCRIBE/PUNSUBSCRIBE */
int redis_unsubscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
zval *z_arr, *z_chan;
HashTable *ht_arr;
smart_string cmdstr = {0};
subscribeContext *sctx = ecalloc(1, sizeof(*sctx));
if (zend_parse_parameters(ZEND_NUM_ARGS(), "a", &z_arr) == FAILURE) {
efree(sctx);
return FAILURE;
}
ht_arr = Z_ARRVAL_P(z_arr);
sctx->argc = zend_hash_num_elements(ht_arr);
if (sctx->argc == 0) {
efree(sctx);
return FAILURE;
}
redis_cmd_init_sstr(&cmdstr, sctx->argc, kw, strlen(kw));
ZEND_HASH_FOREACH_VAL(ht_arr, z_chan) {
char *key = Z_STRVAL_P(z_chan);
size_t key_len = Z_STRLEN_P(z_chan);
int key_free;
key_free = redis_key_prefix(redis_sock, &key, &key_len);
redis_cmd_append_sstr(&cmdstr, key, key_len);
if (key_free) efree(key);
} ZEND_HASH_FOREACH_END();
// Push out vals
*cmd_len = cmdstr.len;
*cmd = cmdstr.c;
*ctx = (void*)sctx;
return SUCCESS;
}
/* ZRANGEBYLEX/ZREVRANGEBYLEX */
int redis_zrangebylex_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *key, *min, *max;
size_t key_len, min_len, max_len;
int argc = ZEND_NUM_ARGS();
zend_long offset, count;
/* We need either 3 or 5 arguments for this to be valid */
if (argc != 3 && argc != 5) {
php_error_docref(0, E_WARNING, "Must pass either 3 or 5 arguments");
return FAILURE;
}
if (zend_parse_parameters(argc, "sss|ll", &key, &key_len, &min, &min_len,
&max, &max_len, &offset, &count) == FAILURE)
{
return FAILURE;
}
/* min and max must start with '(' or '[', or be either '-' or '+' */
if (min_len < 1 || max_len < 1 ||
(min[0] != '(' && min[0] != '[' &&
(min[0] != '-' || min_len > 1) && (min[0] != '+' || min_len > 1)) ||
(max[0] != '(' && max[0] != '[' &&
(max[0] != '-' || max_len > 1) && (max[0] != '+' || max_len > 1)))
{
php_error_docref(0, E_WARNING,
"min and max arguments must start with '[' or '('");
return FAILURE;
}
/* Construct command */
if (argc == 3) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kss", key, key_len, min, min_len,
max, max_len);
} else {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ksssll", key, key_len, min, min_len,
max, max_len, "LIMIT", 5, offset, count);
}
return SUCCESS;
}
/* Validate ZLEX* min/max argument strings */
static int validate_zlex_arg(const char *arg, size_t len) {
return (len > 1 && (*arg == '[' || *arg == '(')) ||
(len == 1 && (*arg == '+' || *arg == '-'));
}
/* ZLEXCOUNT/ZREMRANGEBYLEX */
int redis_gen_zlex_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *key, *min, *max;
size_t key_len, min_len, max_len;
/* Parse args */
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss", &key, &key_len,
&min, &min_len, &max, &max_len) == FAILURE)
{
return FAILURE;
}
/* Quick sanity check on min/max */
if (!validate_zlex_arg(min, min_len) || !validate_zlex_arg(max, max_len)) {
php_error_docref(NULL, E_WARNING,
"Min/Max args can be '-' or '+', or start with '[' or '('");
return FAILURE;
}
/* Construct command */
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kss", key, key_len, min, min_len,
max, max_len);
return SUCCESS;
}
/* EVAL and EVALSHA */
int redis_eval_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *lua;
int argc = 0;
zval *z_arr = NULL, *z_ele;
HashTable *ht_arr;
zend_long num_keys = 0;
smart_string cmdstr = {0};
size_t lua_len;
zend_string *zstr;
short prevslot = -1;
/* Parse args */
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|al", &lua, &lua_len,
&z_arr, &num_keys) == FAILURE)
{
return FAILURE;
}
/* Grab arg count */
if (z_arr != NULL) {
ht_arr = Z_ARRVAL_P(z_arr);
argc = zend_hash_num_elements(ht_arr);
}
/* EVAL[SHA] {script || sha1} {num keys} */
redis_cmd_init_sstr(&cmdstr, 2 + argc, kw, strlen(kw));
redis_cmd_append_sstr(&cmdstr, lua, lua_len);
redis_cmd_append_sstr_long(&cmdstr, num_keys);
// Iterate over our args if we have any
if (argc > 0) {
ZEND_HASH_FOREACH_VAL(ht_arr, z_ele) {
zstr = zval_get_string(z_ele);
/* If we're still on a key, prefix it check slot */
if (num_keys-- > 0) {
redis_cmd_append_sstr_key(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr), redis_sock, slot);
/* If we have been passed a slot, all keys must match */
if (slot) {
if (prevslot != -1 && prevslot != *slot) {
zend_string_release(zstr);
php_error_docref(0, E_WARNING, "All keys do not map to the same slot");
return FAILURE;
}
prevslot = *slot;
}
} else {
redis_cmd_append_sstr(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
}
zend_string_release(zstr);
} ZEND_HASH_FOREACH_END();
} else {
/* Any slot will do */
CMD_RAND_SLOT(slot);
}
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
/* Commands that take a key followed by a variable list of serializable
* values (RPUSH, LPUSH, SADD, SREM, etc...) */
int redis_key_varval_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
zval *z_args;
smart_string cmdstr = {0};
size_t i;
int argc = ZEND_NUM_ARGS();
// We at least need a key and one value
if (argc < 2) {
return FAILURE;
}
// Make sure we at least have a key, and we can get other args
z_args = emalloc(argc * sizeof(zval));
if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) {
efree(z_args);
return FAILURE;
}
/* Initialize our command */
redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw));
/* Append key */
zend_string *zstr = zval_get_string(&z_args[0]);
redis_cmd_append_sstr_key(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr), redis_sock, slot);
zend_string_release(zstr);
/* Add members */
for (i = 1; i < argc; i++ ){
redis_cmd_append_sstr_zval(&cmdstr, &z_args[i], redis_sock);
}
// Push out values
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
// Cleanup arg array
efree(z_args);
// Success!
return SUCCESS;
}
/* Commands that take a key and then an array of values */
#define VAL_TYPE_VALUES 1
#define VAL_TYPE_STRINGS 2
static int gen_key_arr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, int valtype, char **cmd, int *cmd_len,
short *slot, void **ctx)
{
zval *z_arr, *z_val;
HashTable *ht_arr;
smart_string cmdstr = {0};
zend_string *zstr;
int key_free, val_free, argc = 1;
size_t val_len, key_len;
char *key, *val;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa", &key, &key_len,
&z_arr) == FAILURE ||
zend_hash_num_elements(Z_ARRVAL_P(z_arr)) == 0)
{
return FAILURE;
}
/* Start constructing our command */
ht_arr = Z_ARRVAL_P(z_arr);
argc += zend_hash_num_elements(ht_arr);
redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw));
/* Prefix if required and append the key name */
key_free = redis_key_prefix(redis_sock, &key, &key_len);
redis_cmd_append_sstr(&cmdstr, key, key_len);
CMD_SET_SLOT(slot, key, key_len);
if (key_free) efree(key);
/* Iterate our hash table, serializing and appending values */
assert(valtype == VAL_TYPE_VALUES || valtype == VAL_TYPE_STRINGS);
ZEND_HASH_FOREACH_VAL(ht_arr, z_val) {
if (valtype == VAL_TYPE_VALUES) {
val_free = redis_pack(redis_sock, z_val, &val, &val_len);
redis_cmd_append_sstr(&cmdstr, val, val_len);
if (val_free) efree(val);
} else {
zstr = zval_get_string(z_val);
redis_cmd_append_sstr(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
zend_string_release(zstr);
}
} ZEND_HASH_FOREACH_END();
*cmd_len = cmdstr.len;
*cmd = cmdstr.c;
return SUCCESS;
}
int redis_key_val_arr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
return gen_key_arr_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, kw,
VAL_TYPE_VALUES, cmd, cmd_len, slot, ctx);
}
int redis_key_str_arr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
return gen_key_arr_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, kw,
VAL_TYPE_STRINGS, cmd, cmd_len, slot, ctx);
}
/* Generic function that takes a variable number of keys, with an optional
* timeout value. This can handle various SUNION/SUNIONSTORE/BRPOP type
* commands. */
static int gen_varkey_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, int kw_len, int min_argc, int has_timeout,
char **cmd, int *cmd_len, short *slot)
{
zval *z_args, *z_ele;
HashTable *ht_arr;
char *key;
int key_free, i, tail;
size_t key_len;
int single_array = 0, argc = ZEND_NUM_ARGS();
smart_string cmdstr = {0};
long timeout = 0;
short kslot = -1;
zend_string *zstr;
if (argc < min_argc) {
zend_wrong_param_count();
return FAILURE;
}
// Allocate args
z_args = emalloc(argc * sizeof(zval));
if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) {
efree(z_args);
return FAILURE;
}
// Handle our "single array" case
if (has_timeout == 0) {
single_array = argc==1 && Z_TYPE(z_args[0]) == IS_ARRAY;
} else {
single_array = argc==2 && Z_TYPE(z_args[0]) == IS_ARRAY &&
Z_TYPE(z_args[1]) == IS_LONG;
timeout = Z_LVAL(z_args[1]);
}
// If we're running a single array, rework args
if (single_array) {
ht_arr = Z_ARRVAL(z_args[0]);
argc = zend_hash_num_elements(ht_arr);
if (has_timeout) argc++;
efree(z_args);
z_args = NULL;
/* If the array is empty, we can simply abort */
if (argc == 0) return FAILURE;
}
// Begin construction of our command
redis_cmd_init_sstr(&cmdstr, argc, kw, kw_len);
if (single_array) {
ZEND_HASH_FOREACH_VAL(ht_arr, z_ele) {
zstr = zval_get_string(z_ele);
key = ZSTR_VAL(zstr);
key_len = ZSTR_LEN(zstr);
key_free = redis_key_prefix(redis_sock, &key, &key_len);
// Protect against CROSSLOT errors
if (slot) {
if (kslot == -1) {
kslot = cluster_hash_key(key, key_len);
} else if (cluster_hash_key(key,key_len)!=kslot) {
zend_string_release(zstr);
if (key_free) efree(key);
php_error_docref(NULL, E_WARNING,
"Not all keys hash to the same slot!");
return FAILURE;
}
}
// Append this key, free it if we prefixed
redis_cmd_append_sstr(&cmdstr, key, key_len);
zend_string_release(zstr);
if (key_free) efree(key);
} ZEND_HASH_FOREACH_END();
if (has_timeout) {
redis_cmd_append_sstr_long(&cmdstr, timeout);
}
} else {
if (has_timeout && Z_TYPE(z_args[argc-1])!=IS_LONG) {
php_error_docref(NULL, E_ERROR,
"Timeout value must be a LONG");
efree(z_args);
return FAILURE;
}
tail = has_timeout ? argc-1 : argc;
for(i = 0; i < tail; i++) {
zstr = zval_get_string(&z_args[i]);
key = ZSTR_VAL(zstr);
key_len = ZSTR_LEN(zstr);
key_free = redis_key_prefix(redis_sock, &key, &key_len);
/* Protect against CROSSSLOT errors if we've got a slot */
if (slot) {
if ( kslot == -1) {
kslot = cluster_hash_key(key, key_len);
} else if (cluster_hash_key(key,key_len)!=kslot) {
php_error_docref(NULL, E_WARNING,
"Not all keys hash to the same slot");
zend_string_release(zstr);
if (key_free) efree(key);
efree(z_args);
return FAILURE;
}
}
// Append this key
redis_cmd_append_sstr(&cmdstr, key, key_len);
zend_string_release(zstr);
if (key_free) efree(key);
}
if (has_timeout) {
redis_cmd_append_sstr_long(&cmdstr, Z_LVAL(z_args[tail]));
}
// Cleanup args
efree(z_args);
}
// Push out parameters
if (slot) *slot = kslot;
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
/* Generic handling of every blocking pop command (BLPOP, BZPOP[MIN/MAX], etc */
int redis_blocking_pop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, kw,
strlen(kw), 2, 2, cmd, cmd_len, slot);
}
/*
* Commands with specific signatures or that need unique functions because they
* have specific processing (argument validation, etc) that make them unique
*/
/* Attempt to pull a long expiry from a zval. We're more restrictave than zval_get_long
* because that function will return integers from things like open file descriptors
* which should simply fail as a TTL */
static int redis_try_get_expiry(zval *zv, zend_long *lval) {
double dval;
/* Success on an actual long or double */
if (Z_TYPE_P(zv) == IS_LONG || Z_TYPE_P(zv) == IS_DOUBLE) {
*lval = zval_get_long(zv);
return SUCCESS;
}
/* Automatically fail if we're not a string */
if (Z_TYPE_P(zv) != IS_STRING)
return FAILURE;
/* Attempt to get a long from the string */
switch (is_numeric_string(Z_STRVAL_P(zv), Z_STRLEN_P(zv), lval, &dval, 0)) {
case IS_DOUBLE:
*lval = dval;
REDIS_FALLTHROUGH;
case IS_LONG:
return SUCCESS;
default:
return FAILURE;
}
}
/* SET */
int redis_set_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
smart_string cmdstr = {0};
zval *z_value, *z_opts=NULL;
char *key = NULL, *exp_type = NULL, *set_type = NULL;
long exp_set = 0, keep_ttl = 0;
zend_long expire = -1;
zend_bool get = 0;
size_t key_len;
// Make sure the function is being called correctly
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz|z", &key, &key_len,
&z_value, &z_opts) == FAILURE)
{
return FAILURE;
}
// Check for an options array
if (z_opts && Z_TYPE_P(z_opts) == IS_ARRAY) {
HashTable *kt = Z_ARRVAL_P(z_opts);
zend_string *zkey;
zval *v;
/* Iterate our option array */
ZEND_HASH_FOREACH_STR_KEY_VAL(kt, zkey, v) {
ZVAL_DEREF(v);
/* Detect PX or EX argument and validate timeout */
if (zkey && ZSTR_IS_EX_PX_ARG(zkey)) {
exp_set = 1;
/* Set expire type */
exp_type = ZSTR_VAL(zkey);
/* Try to extract timeout */
if (Z_TYPE_P(v) == IS_LONG) {
expire = Z_LVAL_P(v);
} else if (Z_TYPE_P(v) == IS_STRING) {
expire = atol(Z_STRVAL_P(v));
}
} else if (Z_TYPE_P(v) == IS_STRING) {
if (ZVAL_STRICMP_STATIC(v, "KEEPTTL")) {
keep_ttl = 1;
} else if (ZVAL_STRICMP_STATIC((v), "GET")) {
get = 1;
} else if (ZVAL_IS_NX_XX_ARG(v)) {
set_type = Z_STRVAL_P(v);
}
}
} ZEND_HASH_FOREACH_END();
} else if (z_opts && Z_TYPE_P(z_opts) != IS_NULL) {
if (redis_try_get_expiry(z_opts, &expire) == FAILURE) {
php_error_docref(NULL, E_WARNING, "Expire must be a long, double, or a numeric string");
return FAILURE;
}
exp_set = 1;
}
/* Protect the user from syntax errors but give them some info about what's wrong */
if (exp_set && expire < 1) {
php_error_docref(NULL, E_WARNING, "EXPIRE can't be < 1");
return FAILURE;
} else if (exp_type && keep_ttl) {
php_error_docref(NULL, E_WARNING, "KEEPTTL can't be combined with EX or PX option");
return FAILURE;
}
/* Backward compatibility: If we are passed no options except an EXPIRE ttl, we
* actually execute a SETEX command */
if (expire > 0 && !exp_type && !set_type && !keep_ttl) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "SETEX", "klv", key, key_len, expire, z_value);
return SUCCESS;
}
/* Calculate argc based on options set */
int argc = 2 + (exp_type ? 2 : 0) + (set_type != NULL) + (keep_ttl != 0) + get;
/* Initial SET <key> <value> */
redis_cmd_init_sstr(&cmdstr, argc, "SET", 3);
redis_cmd_append_sstr_key(&cmdstr, key, key_len, redis_sock, slot);
redis_cmd_append_sstr_zval(&cmdstr, z_value, redis_sock);
if (exp_type) {
redis_cmd_append_sstr(&cmdstr, exp_type, strlen(exp_type));
redis_cmd_append_sstr_long(&cmdstr, (long)expire);
}
if (set_type)
redis_cmd_append_sstr(&cmdstr, set_type, strlen(set_type));
if (keep_ttl)
redis_cmd_append_sstr(&cmdstr, "KEEPTTL", 7);
if (get) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "GET");
*ctx = PHPREDIS_CTX_PTR;
}
/* Push command and length to the caller */
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
/* BRPOPLPUSH */
int redis_brpoplpush_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key1, *key2;
size_t key1_len, key2_len;
int key1_free, key2_free;
short slot1, slot2;
zend_long timeout;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssl", &key1, &key1_len,
&key2, &key2_len, &timeout) == FAILURE)
{
return FAILURE;
}
// Key prefixing
key1_free = redis_key_prefix(redis_sock, &key1, &key1_len);
key2_free = redis_key_prefix(redis_sock, &key2, &key2_len);
// In cluster mode, verify the slots match
if (slot) {
slot1 = cluster_hash_key(key1, key1_len);
slot2 = cluster_hash_key(key2, key2_len);
if (slot1 != slot2) {
php_error_docref(NULL, E_WARNING,
"Keys hash to different slots!");
if (key1_free) efree(key1);
if (key2_free) efree(key2);
return FAILURE;
}
// Both slots are the same
*slot = slot1;
}
// Consistency with Redis, if timeout < 0 use RPOPLPUSH
if (timeout < 0) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "RPOPLPUSH", "ss", key1, key1_len,
key2, key2_len);
} else {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "BRPOPLPUSH", "ssd", key1, key1_len,
key2, key2_len, timeout);
}
if (key1_free) efree(key1);
if (key2_free) efree(key2);
return SUCCESS;
}
/* To maintain backward compatibility with earlier versions of phpredis, we
* allow for an optional "increment by" argument for INCR and DECR even though
* that's not how Redis proper works */
#define TYPE_INCR 0
#define TYPE_DECR 1
/* Handle INCR(BY) and DECR(BY) depending on optional increment value */
static int
redis_atomic_increment(INTERNAL_FUNCTION_PARAMETERS, int type,
RedisSock *redis_sock, char **cmd, int *cmd_len,
short *slot, void **ctx)
{
char *key;
size_t key_len;
zend_long val = 1;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &key, &key_len,
&val) == FAILURE)
{
return FAILURE;
}
/* If our value is 1 we use INCR/DECR. For other values, treat the call as
* an INCRBY or DECRBY call */
if (type == TYPE_INCR) {
if (val == 1) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "INCR", "k", key, key_len);
} else {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "INCRBY", "kl", key, key_len, val);
}
} else {
if (val == 1) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "DECR", "k", key, key_len);
} else {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "DECRBY", "kl", key, key_len, val);
}
}
/* Success */
return SUCCESS;
}
/* INCR */
int redis_incr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU,
TYPE_INCR, redis_sock, cmd, cmd_len, slot, ctx);
}
/* DECR */
int redis_decr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU,
TYPE_DECR, redis_sock, cmd, cmd_len, slot, ctx);
}
/* HINCRBY */
int redis_hincrby_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key, *mem;
size_t key_len, mem_len;
zend_long byval;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssl", &key, &key_len,
&mem, &mem_len, &byval) == FAILURE)
{
return FAILURE;
}
// Construct command
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "HINCRBY", "ksl", key, key_len, mem, mem_len, byval);
// Success
return SUCCESS;
}
/* HINCRBYFLOAT */
int redis_hincrbyfloat_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key, *mem;
size_t key_len, mem_len;
double byval;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssd", &key, &key_len,
&mem, &mem_len, &byval) == FAILURE)
{
return FAILURE;
}
// Construct command
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "HINCRBYFLOAT", "ksf", key, key_len, mem,
mem_len, byval);
// Success
return SUCCESS;
}
/* HMGET */
int redis_hmget_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key;
zval *z_arr, *z_mems, *z_mem;
int i, count, valid = 0, key_free;
size_t key_len;
HashTable *ht_arr;
smart_string cmdstr = {0};
// Parse arguments
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa", &key, &key_len,
&z_arr) == FAILURE)
{
return FAILURE;
}
// Our HashTable
ht_arr = Z_ARRVAL_P(z_arr);
// We can abort if we have no elements
if ((count = zend_hash_num_elements(ht_arr)) == 0) {
return FAILURE;
}
// Allocate memory for mems+1 so we can have a sentinel
z_mems = ecalloc(count + 1, sizeof(zval));
// Iterate over our member array
ZEND_HASH_FOREACH_VAL(ht_arr, z_mem) {
ZVAL_DEREF(z_mem);
// We can only handle string or long values here
if ((Z_TYPE_P(z_mem) == IS_STRING && Z_STRLEN_P(z_mem) > 0)
|| Z_TYPE_P(z_mem) == IS_LONG
) {
// Copy into our member array
ZVAL_ZVAL(&z_mems[valid], z_mem, 1, 0);
// Increment the member count to actually send
valid++;
}
} ZEND_HASH_FOREACH_END();
// If nothing was valid, fail
if (valid == 0) {
efree(z_mems);
return FAILURE;
}
// Sentinel so we can free this even if it's used and then we discard
// the transaction manually or there is a transaction failure
ZVAL_NULL(&z_mems[valid]);
// Start command construction
redis_cmd_init_sstr(&cmdstr, valid+1, "HMGET", sizeof("HMGET")-1);
// Prefix our key
key_free = redis_key_prefix(redis_sock, &key, &key_len);
redis_cmd_append_sstr(&cmdstr, key, key_len);
// Iterate over members, appending as arguments
for(i = 0; i< valid; i++) {
zend_string *zstr = zval_get_string(&z_mems[i]);
redis_cmd_append_sstr(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
zend_string_release(zstr);
}
// Set our slot
CMD_SET_SLOT(slot,key,key_len);
// Free our key if we prefixed it
if (key_free) efree(key);
// Push out command, length, and key context
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
*ctx = (void*)z_mems;
// Success!
return SUCCESS;
}
/* HMSET */
int redis_hmset_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key;
int key_free, count;
size_t key_len;
zend_ulong idx;
zval *z_arr;
HashTable *ht_vals;
smart_string cmdstr = {0};
zend_string *zkey;
zval *z_val;
// Parse args
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa", &key, &key_len,
&z_arr) == FAILURE)
{
return FAILURE;
}
// We can abort if we have no fields
if ((count = zend_hash_num_elements(Z_ARRVAL_P(z_arr))) == 0) {
return FAILURE;
}
// Prefix our key
key_free = redis_key_prefix(redis_sock, &key, &key_len);
// Grab our array as a HashTable
ht_vals = Z_ARRVAL_P(z_arr);
// Initialize our HMSET command (key + 2x each array entry), add key
redis_cmd_init_sstr(&cmdstr, 1+(count*2), "HMSET", sizeof("HMSET")-1);
redis_cmd_append_sstr(&cmdstr, key, key_len);
// Start traversing our key => value array
ZEND_HASH_FOREACH_KEY_VAL(ht_vals, idx, zkey, z_val) {
char *mem, *val, kbuf[40];
size_t val_len;
int val_free;
unsigned int mem_len;
// If the hash key is an integer, convert it to a string
if (zkey) {
mem_len = ZSTR_LEN(zkey);
mem = ZSTR_VAL(zkey);
} else {
mem_len = snprintf(kbuf, sizeof(kbuf), ZEND_LONG_FMT, idx);
mem = (char*)kbuf;
}
// Serialize value (if directed)
val_free = redis_pack(redis_sock, z_val, &val, &val_len);
// Append the key and value to our command
redis_cmd_append_sstr(&cmdstr, mem, mem_len);
redis_cmd_append_sstr(&cmdstr, val, val_len);
// Free our value if we serialized it
if (val_free) efree(val);
} ZEND_HASH_FOREACH_END();
// Set slot if directed
CMD_SET_SLOT(slot,key,key_len);
// Free our key if we prefixed it
if (key_free) efree(key);
// Push return pointers
*cmd_len = cmdstr.len;
*cmd = cmdstr.c;
// Success!
return SUCCESS;
}
/* HSTRLEN */
int
redis_hstrlen_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key, *field;
size_t key_len, field_len;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &key, &key_len,
&field, &field_len) == FAILURE
) {
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "HSTRLEN", "ks", key, key_len, field, field_len);
return SUCCESS;
}
/* BITPOS */
int redis_bitpos_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key;
int argc;
zend_long bit, start, end;
size_t key_len;
argc = ZEND_NUM_ARGS();
if (zend_parse_parameters(argc, "sl|ll", &key, &key_len, &bit,
&start, &end) == FAILURE)
{
return FAILURE;
}
// Prevalidate bit
if (bit != 0 && bit != 1) {
return FAILURE;
}
// Construct command based on arg count
if (argc == 2) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "BITPOS", "kd", key, key_len, bit);
} else if (argc == 3) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "BITPOS", "kdd", key, key_len, bit,
start);
} else {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "BITPOS", "kddd", key, key_len, bit,
start, end);
}
return SUCCESS;
}
/* BITOP */
int redis_bitop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
zval *z_args;
char *key;
size_t key_len;
int i, key_free, argc = ZEND_NUM_ARGS();
smart_string cmdstr = {0};
short kslot;
zend_string *zstr;
// Allocate space for args, parse them as an array
z_args = emalloc(argc * sizeof(zval));
if (zend_get_parameters_array(ht, argc, z_args) == FAILURE ||
argc < 3 || Z_TYPE(z_args[0]) != IS_STRING)
{
efree(z_args);
return FAILURE;
}
// If we were passed a slot pointer, init to a sentinel value
if (slot) *slot = -1;
// Initialize command construction, add our operation argument
redis_cmd_init_sstr(&cmdstr, argc, "BITOP", sizeof("BITOP")-1);
redis_cmd_append_sstr(&cmdstr, Z_STRVAL(z_args[0]), Z_STRLEN(z_args[0]));
// Now iterate over our keys argument
for (i = 1; i < argc; i++) {
// Make sure we've got a string
zstr = zval_get_string(&z_args[i]);
// Grab this key and length
key = ZSTR_VAL(zstr);
key_len = ZSTR_LEN(zstr);
// Prefix key, append
key_free = redis_key_prefix(redis_sock, &key, &key_len);
redis_cmd_append_sstr(&cmdstr, key, key_len);
// Verify slot if this is a Cluster request
if (slot) {
kslot = cluster_hash_key(key, key_len);
if (*slot == -1 || kslot != *slot) {
php_error_docref(NULL, E_WARNING,
"Warning, not all keys hash to the same slot!");
zend_string_release(zstr);
if (key_free) efree(key);
efree(z_args);
return FAILURE;
}
*slot = kslot;
}
zend_string_release(zstr);
if (key_free) efree(key);
}
// Free our argument array
efree(z_args);
// Push out variables
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
/* BITCOUNT */
int redis_bitcount_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key;
size_t key_len;
zend_long start = 0, end = -1;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|ll", &key, &key_len,
&start, &end) == FAILURE)
{
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "BITCOUNT", "kdd", key, key_len,
(int)start, (int)end);
return SUCCESS;
}
/* PFADD and PFMERGE are the same except that in one case we serialize,
* and in the other case we key prefix */
static int redis_gen_pf_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, int kw_len, int is_keys, char **cmd,
int *cmd_len, short *slot)
{
zval *z_arr, *z_ele;
HashTable *ht_arr;
smart_string cmdstr = {0};
char *mem, *key;
int key_free, mem_free, argc=1;
size_t key_len, mem_len;
zend_string *zstr;
// Parse arguments
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa", &key, &key_len,
&z_arr) == FAILURE)
{
return FAILURE;
}
// Grab HashTable, count total argc
ht_arr = Z_ARRVAL_P(z_arr);
argc += zend_hash_num_elements(ht_arr);
// We need at least two arguments
if (argc < 2) {
return FAILURE;
}
// Prefix key, set initial hash slot
key_free = redis_key_prefix(redis_sock, &key, &key_len);
if (slot) *slot = cluster_hash_key(key, key_len);
// Start command construction
redis_cmd_init_sstr(&cmdstr, argc, kw, kw_len);
redis_cmd_append_sstr(&cmdstr, key, key_len);
// Free key if we prefixed
if (key_free) efree(key);
// Now iterate over the rest of our keys or values
ZEND_HASH_FOREACH_VAL(ht_arr, z_ele) {
// Prefix keys, serialize values
if (is_keys) {
zstr = zval_get_string(z_ele);
mem = ZSTR_VAL(zstr);
mem_len = ZSTR_LEN(zstr);
// Key prefix
mem_free = redis_key_prefix(redis_sock, &mem, &mem_len);
// Verify slot
if (slot && *slot != cluster_hash_key(mem, mem_len)) {
php_error_docref(0, E_WARNING,
"All keys must hash to the same slot!");
zend_string_release(zstr);
if (key_free) efree(key);
return FAILURE;
}
} else {
mem_free = redis_pack(redis_sock, z_ele, &mem, &mem_len);
zstr = NULL;
if (!mem_free) {
zstr = zval_get_string(z_ele);
mem = ZSTR_VAL(zstr);
mem_len = ZSTR_LEN(zstr);
}
}
// Append our key or member
redis_cmd_append_sstr(&cmdstr, mem, mem_len);
// Clean up any allocated memory
if (zstr) zend_string_release(zstr);
if (mem_free) efree(mem);
} ZEND_HASH_FOREACH_END();
// Push output arguments
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
/* PFADD */
int redis_pfadd_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return redis_gen_pf_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
"PFADD", sizeof("PFADD")-1, 0, cmd, cmd_len, slot);
}
/* PFMERGE */
int redis_pfmerge_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return redis_gen_pf_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
"PFMERGE", sizeof("PFMERGE")-1, 1, cmd, cmd_len, slot);
}
/* PFCOUNT */
int redis_pfcount_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
zval *z_keys, *z_key;
HashTable *ht_keys;
smart_string cmdstr = {0};
int num_keys, key_free;
size_t key_len;
char *key;
short kslot=-1;
zend_string *zstr;
if (zend_parse_parameters(ZEND_NUM_ARGS(),"z",&z_keys) == FAILURE) {
return FAILURE;
}
/* If we were passed an array of keys, iterate through them prefixing if
* required and capturing lengths and if we need to free them. Otherwise
* attempt to treat the argument as a string and just pass one */
if (Z_TYPE_P(z_keys) == IS_ARRAY) {
/* Grab key hash table and the number of keys */
ht_keys = Z_ARRVAL_P(z_keys);
num_keys = zend_hash_num_elements(ht_keys);
/* There is no reason to send zero keys */
if (num_keys == 0) {
return FAILURE;
}
/* Initialize the command with our number of arguments */
redis_cmd_init_sstr(&cmdstr, num_keys, "PFCOUNT", sizeof("PFCOUNT")-1);
/* Append our key(s) */
ZEND_HASH_FOREACH_VAL(ht_keys, z_key) {
/* Turn our value into a string if it isn't one */
zstr = zval_get_string(z_key);
key = ZSTR_VAL(zstr);
key_len = ZSTR_LEN(zstr);
/* Append this key to our command */
key_free = redis_key_prefix(redis_sock, &key, &key_len);
redis_cmd_append_sstr(&cmdstr, key, key_len);
/* Protect against CROSSLOT errors */
if (slot) {
if (kslot == -1) {
kslot = cluster_hash_key(key, key_len);
} else if (cluster_hash_key(key,key_len)!=kslot) {
zend_string_release(zstr);
if (key_free) efree(key);
efree(cmdstr.c);
php_error_docref(NULL, E_WARNING,
"Not all keys hash to the same slot!");
return FAILURE;
}
}
/* Cleanup */
zend_string_release(zstr);
if (key_free) efree(key);
} ZEND_HASH_FOREACH_END();
} else {
/* Construct our whole command */
redis_cmd_init_sstr(&cmdstr, 1, "PFCOUNT", sizeof("PFCOUNT")-1);
/* Turn our key into a string if it's a different type */
zstr = zval_get_string(z_keys);
key = ZSTR_VAL(zstr);
key_len = ZSTR_LEN(zstr);
key_free = redis_key_prefix(redis_sock, &key, &key_len);
redis_cmd_append_sstr(&cmdstr, key, key_len);
/* Hash our key */
CMD_SET_SLOT(slot, key, key_len);
/* Cleanup */
zend_string_release(zstr);
if (key_free) efree(key);
}
/* Push our command and length to the caller */
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
char *redis_variadic_str_cmd(char *kw, zval *argv, int argc, int *cmd_len) {
smart_string cmdstr = {0};
zend_string *zstr;
int i;
redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw));
for (i = 0; i < argc; i++) {
zstr = zval_get_string(&argv[i]);
redis_cmd_append_sstr_zstr(&cmdstr, zstr);
zend_string_release(zstr);
}
*cmd_len = cmdstr.len;
return cmdstr.c;
}
int redis_auth_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
zend_string *user = NULL, *pass = NULL;
zval *ztest;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "z!", &ztest) == FAILURE ||
redis_extract_auth_info(ztest, &user, &pass) == FAILURE)
{
return FAILURE;
}
/* Construct either AUTH <user> <pass> or AUTH <pass> */
if (user && pass) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "AUTH", "SS", user, pass);
} else {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "AUTH", "S", pass);
}
redis_sock_set_auth(redis_sock, user, pass);
if (user) zend_string_release(user);
if (pass) zend_string_release(pass);
return SUCCESS;
}
/* SETBIT */
int redis_setbit_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key;
size_t key_len;
zend_long offset;
zend_bool val;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "slb", &key, &key_len,
&offset, &val) == FAILURE)
{
return FAILURE;
}
// Validate our offset
if (offset < BITOP_MIN_OFFSET || offset > BITOP_MAX_OFFSET) {
php_error_docref(0, E_WARNING,
"Invalid OFFSET for bitop command (must be between 0-2^32-1)");
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "SETBIT", "kld", key, key_len, offset, (int)val);
return SUCCESS;
}
int
redis_lmove_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *src, *dst, *from, *to;
size_t src_len, dst_len, from_len, to_len;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssss",
&src, &src_len, &dst, &dst_len,
&from, &from_len, &to, &to_len) == FAILURE
) {
return FAILURE;
}
// Validate wherefrom/whereto
if (strcasecmp(from, "left") != 0 && strcasecmp(from, "right") != 0) {
php_error_docref(NULL, E_WARNING,
"Wherefrom argument must be either 'LEFT' or 'RIGHT'");
return FAILURE;
} else if (strcasecmp(to, "left") != 0 && strcasecmp(to, "right") != 0) {
php_error_docref(NULL, E_WARNING,
"Whereto argument must be either 'LEFT' or 'RIGHT'");
return FAILURE;
}
/* Construct command */
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "LMOVE", "kkss",
src, src_len, dst, dst_len,
from, from_len, to, to_len);
return SUCCESS;
}
/* LINSERT */
int redis_linsert_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key, *pos;
size_t key_len, pos_len;
zval *z_val, *z_pivot;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sszz", &key, &key_len,
&pos, &pos_len, &z_pivot, &z_val) == FAILURE)
{
return FAILURE;
}
// Validate position
if (strcasecmp(pos, "after") && strcasecmp(pos, "before")) {
php_error_docref(NULL, E_WARNING,
"Position must be either 'BEFORE' or 'AFTER'");
return FAILURE;
}
/* Construct command */
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "LINSERT", "ksvv", key, key_len, pos,
pos_len, z_pivot, z_val);
// Success
return SUCCESS;
}
/* LREM */
int redis_lrem_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key;
size_t key_len;
zend_long count = 0;
zval *z_val;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz|l", &key, &key_len,
&z_val, &count) == FAILURE)
{
return FAILURE;
}
/* Construct command */
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "LREM", "kdv", key, key_len, count, z_val);
// Success!
return SUCCESS;
}
int redis_smove_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *src, *dst;
size_t src_len, dst_len;
int src_free, dst_free;
zval *z_val;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssz", &src, &src_len,
&dst, &dst_len, &z_val) == FAILURE)
{
return FAILURE;
}
src_free = redis_key_prefix(redis_sock, &src, &src_len);
dst_free = redis_key_prefix(redis_sock, &dst, &dst_len);
// Protect against a CROSSSLOT error
if (slot) {
short slot1 = cluster_hash_key(src, src_len);
short slot2 = cluster_hash_key(dst, dst_len);
if (slot1 != slot2) {
php_error_docref(0, E_WARNING,
"Source and destination keys don't hash to the same slot!");
if (src_free) efree(src);
if (dst_free) efree(dst);
return FAILURE;
}
*slot = slot1;
}
// Construct command
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "SMOVE", "ssv", src, src_len, dst,
dst_len, z_val);
// Cleanup
if (src_free) efree(src);
if (dst_free) efree(dst);
// Success!
return SUCCESS;
}
/* Generic command construction for HSET and HSETNX */
static int gen_hset_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot)
{
char *key, *mem;
size_t key_len, mem_len;
zval *z_val;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssz", &key, &key_len,
&mem, &mem_len, &z_val) == FAILURE)
{
return FAILURE;
}
/* Construct command */
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ksv", key, key_len, mem, mem_len, z_val);
// Success
return SUCCESS;
}
/* HSET */
int redis_hset_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return gen_hset_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, "HSET",
cmd, cmd_len, slot);
}
/* HSETNX */
int redis_hsetnx_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return gen_hset_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, "HSETNX",
cmd, cmd_len, slot);
}
/* SRANDMEMBER */
int redis_srandmember_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx,
short *have_count)
{
char *key;
size_t key_len;
zend_long count;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &key, &key_len,
&count) == FAILURE)
{
return FAILURE;
}
// Set our have count flag
*have_count = ZEND_NUM_ARGS() == 2;
// Two args means we have the optional COUNT
if (*have_count) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "SRANDMEMBER", "kl", key, key_len, count);
} else {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "SRANDMEMBER", "k", key, key_len);
}
return SUCCESS;
}
/* ZINCRBY */
int redis_zincrby_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key;
size_t key_len;
double incrby;
zval *z_val;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sdz", &key, &key_len,
&incrby, &z_val) == FAILURE)
{
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "ZINCRBY", "kfv", key, key_len, incrby, z_val);
return SUCCESS;
}
/* SORT */
int redis_sort_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
int *using_store, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
zval *z_opts=NULL, *z_ele, z_argv;
char *key;
HashTable *ht_opts;
smart_string cmdstr = {0};
size_t key_len;
int key_free;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|a", &key, &key_len,
&z_opts) == FAILURE)
{
return FAILURE;
}
// Default that we're not using store
*using_store = 0;
// If we don't have an options array, the command is quite simple
if (!z_opts || zend_hash_num_elements(Z_ARRVAL_P(z_opts)) == 0) {
// Construct command
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "SORT", "k", key, key_len);
/* Not storing */
*using_store = 0;
return SUCCESS;
}
// Create our hash table to hold our sort arguments
array_init(&z_argv);
// SORT <key>
key_free = redis_key_prefix(redis_sock, &key, &key_len);
add_next_index_stringl(&z_argv, key, key_len);
if (key_free) efree(key);
// Set slot
CMD_SET_SLOT(slot,key,key_len);
// Grab the hash table
ht_opts = Z_ARRVAL_P(z_opts);
// Handle BY pattern
if (((z_ele = zend_hash_str_find(ht_opts, "by", sizeof("by") - 1)) != NULL ||
(z_ele = zend_hash_str_find(ht_opts, "BY", sizeof("BY") - 1)) != NULL
) && Z_TYPE_P(z_ele) == IS_STRING
) {
// "BY" option is disabled in cluster
if (slot) {
php_error_docref(NULL, E_WARNING,
"SORT BY option is not allowed in Redis Cluster");
zval_dtor(&z_argv);
return FAILURE;
}
// ... BY <pattern>
add_next_index_stringl(&z_argv, "BY", sizeof("BY") - 1);
add_next_index_stringl(&z_argv, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele));
}
// Handle ASC/DESC option
if (((z_ele = zend_hash_str_find(ht_opts, "sort", sizeof("sort") - 1)) != NULL ||
(z_ele = zend_hash_str_find(ht_opts, "SORT", sizeof("SORT") - 1)) != NULL
) && Z_TYPE_P(z_ele) == IS_STRING
) {
// 'asc'|'desc'
add_next_index_stringl(&z_argv, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele));
}
// STORE option
if (((z_ele = zend_hash_str_find(ht_opts, "store", sizeof("store") - 1)) != NULL ||
(z_ele = zend_hash_str_find(ht_opts, "STORE", sizeof("STORE") - 1)) != NULL
) && Z_TYPE_P(z_ele) == IS_STRING
) {
// Slot verification
int cross_slot = slot && *slot != cluster_hash_key(
Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele));
if (cross_slot) {
php_error_docref(0, E_WARNING,
"Error, SORT key and STORE key have different slots!");
zval_dtor(&z_argv);
return FAILURE;
}
// STORE <key>
add_next_index_stringl(&z_argv, "STORE", sizeof("STORE") - 1);
add_next_index_stringl(&z_argv, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele));
// We are using STORE
*using_store = 1;
}
// GET option
if (((z_ele = zend_hash_str_find(ht_opts, "get", sizeof("get") - 1)) != NULL ||
(z_ele = zend_hash_str_find(ht_opts, "GET", sizeof("GET") - 1)) != NULL
) && (Z_TYPE_P(z_ele) == IS_STRING || Z_TYPE_P(z_ele) == IS_ARRAY)
) {
// Disabled in cluster
if (slot) {
php_error_docref(NULL, E_WARNING,
"GET option for SORT disabled in Redis Cluster");
zval_dtor(&z_argv);
return FAILURE;
}
// If it's a string just add it
if (Z_TYPE_P(z_ele) == IS_STRING) {
add_next_index_stringl(&z_argv, "GET", sizeof("GET") - 1);
add_next_index_stringl(&z_argv, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele));
} else {
int added = 0;
zval *z_key;
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z_ele), z_key) {
// If we can't get the data, or it's not a string, skip
if (z_key == NULL || Z_TYPE_P(z_key) != IS_STRING) {
continue;
}
/* Add get per thing we're getting */
add_next_index_stringl(&z_argv, "GET", sizeof("GET") - 1);
// Add this key to our argv array
add_next_index_stringl(&z_argv, Z_STRVAL_P(z_key), Z_STRLEN_P(z_key));
added++;
} ZEND_HASH_FOREACH_END();
// Make sure we were able to add at least one
if (added == 0) {
php_error_docref(NULL, E_WARNING,
"Array of GET values requested, but none are valid");
zval_dtor(&z_argv);
return FAILURE;
}
}
}
// ALPHA
if (((z_ele = zend_hash_str_find(ht_opts, "alpha", sizeof("alpha") - 1)) != NULL ||
(z_ele = zend_hash_str_find(ht_opts, "ALPHA", sizeof("ALPHA") - 1)) != NULL) &&
zval_is_true(z_ele)
) {
add_next_index_stringl(&z_argv, "ALPHA", sizeof("ALPHA") - 1);
}
// LIMIT <offset> <count>
if (((z_ele = zend_hash_str_find(ht_opts, "limit", sizeof("limit") - 1)) != NULL ||
(z_ele = zend_hash_str_find(ht_opts, "LIMIT", sizeof("LIMIT") - 1)) != NULL
) && Z_TYPE_P(z_ele) == IS_ARRAY
) {
HashTable *ht_off = Z_ARRVAL_P(z_ele);
zval *z_off, *z_cnt;
if ((z_off = zend_hash_index_find(ht_off, 0)) != NULL &&
(z_cnt = zend_hash_index_find(ht_off, 1)) != NULL
) {
if ((Z_TYPE_P(z_off) != IS_STRING && Z_TYPE_P(z_off) != IS_LONG) ||
(Z_TYPE_P(z_cnt) != IS_STRING && Z_TYPE_P(z_cnt) != IS_LONG)
) {
php_error_docref(NULL, E_WARNING,
"LIMIT options on SORT command must be longs or strings");
zval_dtor(&z_argv);
return FAILURE;
}
// Add LIMIT argument
add_next_index_stringl(&z_argv, "LIMIT", sizeof("LIMIT") - 1);
long low, high;
if (Z_TYPE_P(z_off) == IS_STRING) {
low = atol(Z_STRVAL_P(z_off));
} else {
low = Z_LVAL_P(z_off);
}
if (Z_TYPE_P(z_cnt) == IS_STRING) {
high = atol(Z_STRVAL_P(z_cnt));
} else {
high = Z_LVAL_P(z_cnt);
}
// Add our two LIMIT arguments
add_next_index_long(&z_argv, low);
add_next_index_long(&z_argv, high);
}
}
// Start constructing our command
HashTable *ht_argv = Z_ARRVAL_P(&z_argv);
redis_cmd_init_sstr(&cmdstr, zend_hash_num_elements(ht_argv), "SORT",
sizeof("SORT")-1);
// Iterate through our arguments
ZEND_HASH_FOREACH_VAL(ht_argv, z_ele) {
// Args are strings or longs
if (Z_TYPE_P(z_ele) == IS_STRING) {
redis_cmd_append_sstr(&cmdstr,Z_STRVAL_P(z_ele),
Z_STRLEN_P(z_ele));
} else {
redis_cmd_append_sstr_long(&cmdstr, Z_LVAL_P(z_ele));
}
} ZEND_HASH_FOREACH_END();
/* Clean up our arguments array. Note we don't have to free any prefixed
* key as that we didn't duplicate the pointer if we prefixed */
zval_dtor(&z_argv);
// Push our length and command
*cmd_len = cmdstr.len;
*cmd = cmdstr.c;
// Success!
return SUCCESS;
}
/* HDEL */
int redis_hdel_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
zval *z_args;
smart_string cmdstr = {0};
char *arg;
int arg_free, i;
size_t arg_len;
int argc = ZEND_NUM_ARGS();
zend_string *zstr;
// We need at least KEY and one member
if (argc < 2) {
return FAILURE;
}
// Grab arguments as an array
z_args = emalloc(argc * sizeof(zval));
if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) {
efree(z_args);
return FAILURE;
}
// Get first argument (the key) as a string
zstr = zval_get_string(&z_args[0]);
arg = ZSTR_VAL(zstr);
arg_len = ZSTR_LEN(zstr);
// Prefix
arg_free = redis_key_prefix(redis_sock, &arg, &arg_len);
// Start command construction
redis_cmd_init_sstr(&cmdstr, argc, "HDEL", sizeof("HDEL")-1);
redis_cmd_append_sstr(&cmdstr, arg, arg_len);
// Set our slot, free key if we prefixed it
CMD_SET_SLOT(slot,arg,arg_len);
zend_string_release(zstr);
if (arg_free) efree(arg);
// Iterate through the members we're removing
for (i = 1; i < argc; i++) {
zstr = zval_get_string(&z_args[i]);
redis_cmd_append_sstr(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
zend_string_release(zstr);
}
// Push out values
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
// Cleanup
efree(z_args);
// Success!
return SUCCESS;
}
/* ZADD */
int redis_zadd_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
zval *z_args;
char *key, *val, *exp_type = NULL, *range_type = NULL;
size_t key_len, val_len;
int key_free, val_free;
int num = ZEND_NUM_ARGS(), i = 1, argc;
zend_bool ch = 0, incr = 0;
smart_string cmdstr = {0};
zend_string *zstr;
if (num < 3) return FAILURE;
z_args = ecalloc(num, sizeof(zval));
if (zend_get_parameters_array(ht, num, z_args) == FAILURE) {
efree(z_args);
return FAILURE;
}
// Need key, [NX|XX] [LT|GT] [CH] [INCR] score, value, [score, value...] */
if (num % 2 == 0) {
if (Z_TYPE(z_args[1]) != IS_ARRAY) {
efree(z_args);
return FAILURE;
}
zval *z_opt;
ZEND_HASH_FOREACH_VAL(Z_ARRVAL(z_args[1]), z_opt) {
if (Z_TYPE_P(z_opt) == IS_STRING) {
if (ZVAL_IS_NX_XX_ARG(z_opt)) {
exp_type = Z_STRVAL_P(z_opt);
} else if (ZVAL_STRICMP_STATIC(z_opt, "LT") || ZVAL_STRICMP_STATIC(z_opt, "GT")) {
range_type = Z_STRVAL_P(z_opt);
} else if (ZVAL_STRICMP_STATIC(z_opt, "CH")) {
ch = 1;
} else if (ZVAL_STRICMP_STATIC(z_opt, "INCR")) {
if (num > 4) {
// Only one score-element pair can be specified in this mode.
efree(z_args);
return FAILURE;
}
incr = 1;
}
}
} ZEND_HASH_FOREACH_END();
argc = num - 1;
if (exp_type) argc++;
if (range_type) argc++;
argc += ch + incr;
i++;
} else {
argc = num;
}
// Prefix our key
zstr = zval_get_string(&z_args[0]);
key = ZSTR_VAL(zstr);
key_len = ZSTR_LEN(zstr);
key_free = redis_key_prefix(redis_sock, &key, &key_len);
// Start command construction
redis_cmd_init_sstr(&cmdstr, argc, "ZADD", sizeof("ZADD")-1);
redis_cmd_append_sstr(&cmdstr, key, key_len);
// Set our slot, free key if we prefixed it
CMD_SET_SLOT(slot,key,key_len);
zend_string_release(zstr);
if (key_free) efree(key);
if (exp_type) redis_cmd_append_sstr(&cmdstr, exp_type, 2);
if (range_type) redis_cmd_append_sstr(&cmdstr, range_type, 2);
if (ch) redis_cmd_append_sstr(&cmdstr, "CH", 2);
if (incr) redis_cmd_append_sstr(&cmdstr, "INCR", 4);
// Now the rest of our arguments
while (i < num) {
// Append score and member
if (redis_cmd_append_sstr_score(&cmdstr, &z_args[i]) == FAILURE) {
smart_string_free(&cmdstr);
efree(z_args);
return FAILURE;
}
// serialize value if requested
val_free = redis_pack(redis_sock, &z_args[i+1], &val, &val_len);
redis_cmd_append_sstr(&cmdstr, val, val_len);
// Free value if we serialized
if (val_free) efree(val);
i += 2;
}
// Push output values
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
// Cleanup args
efree(z_args);
return SUCCESS;
}
/* OBJECT */
int redis_object_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
REDIS_REPLY_TYPE *rtype, char **cmd, int *cmd_len,
short *slot, void **ctx)
{
char *key, *subcmd;
size_t key_len, subcmd_len;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &subcmd,
&subcmd_len, &key, &key_len) == FAILURE)
{
return FAILURE;
}
// Format our command
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "OBJECT", "sk", subcmd, subcmd_len, key, key_len);
// Push the reply type to our caller
if (subcmd_len == 8 && (!strncasecmp(subcmd,"refcount",8) ||
!strncasecmp(subcmd,"idletime",8)))
{
*rtype = TYPE_INT;
} else if (subcmd_len == 8 && !strncasecmp(subcmd, "encoding", 8)) {
*rtype = TYPE_BULK;
} else {
php_error_docref(NULL, E_WARNING,
"Invalid subcommand sent to OBJECT");
efree(*cmd);
return FAILURE;
}
// Success
return SUCCESS;
}
/* GEODIST */
int redis_geodist_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key, *source, *dest, *unit = NULL;
size_t keylen, sourcelen, destlen, unitlen;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|s", &key, &keylen,
&source, &sourcelen, &dest, &destlen, &unit,
&unitlen) == FAILURE)
{
return FAILURE;
}
/* Construct command */
if (unit != NULL) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "GEODIST", "ksss", key, keylen, source,
sourcelen, dest, destlen, unit, unitlen);
} else {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "GEODIST", "kss", key, keylen, source,
sourcelen, dest, destlen);
}
return SUCCESS;
}
geoStoreType get_georadius_store_type(zend_string *key) {
if (ZSTR_LEN(key) == 5 && !strcasecmp(ZSTR_VAL(key), "store")) {
return STORE_COORD;
} else if (ZSTR_LEN(key) == 9 && !strcasecmp(ZSTR_VAL(key), "storedist")) {
return STORE_DIST;
}
return STORE_NONE;
}
/* Helper function to extract optional arguments for GEORADIUS and GEORADIUSBYMEMBER */
static int get_georadius_opts(HashTable *ht, geoOptions *opts) {
char *optstr;
zend_string *zkey;
zval *optval;
/* Iterate over our argument array, collating which ones we have */
ZEND_HASH_FOREACH_STR_KEY_VAL(ht, zkey, optval) {
ZVAL_DEREF(optval);
/* If the key is numeric it's a non value option */
if (zkey) {
if (ZSTR_LEN(zkey) == 5 && !strcasecmp(ZSTR_VAL(zkey), "count")) {
if (Z_TYPE_P(optval) != IS_LONG || Z_LVAL_P(optval) <= 0) {
php_error_docref(NULL, E_WARNING,
"COUNT must be an integer > 0!");
if (opts->key) zend_string_release(opts->key);
return FAILURE;
}
/* Set our count */
opts->count = Z_LVAL_P(optval);
} else if (opts->store == STORE_NONE) {
opts->store = get_georadius_store_type(zkey);
if (opts->store != STORE_NONE) {
opts->key = zval_get_string(optval);
}
}
} else {
/* Option needs to be a string */
if (Z_TYPE_P(optval) != IS_STRING) continue;
optstr = Z_STRVAL_P(optval);
if (!strcasecmp(optstr, "withcoord")) {
opts->withcoord = 1;
} else if (!strcasecmp(optstr, "withdist")) {
opts->withdist = 1;
} else if (!strcasecmp(optstr, "withhash")) {
opts->withhash = 1;
} else if (!strcasecmp(optstr, "asc")) {
opts->sort = SORT_ASC;
} else if (!strcasecmp(optstr, "desc")) {
opts->sort = SORT_DESC;
}
}
} ZEND_HASH_FOREACH_END();
/* STORE and STOREDIST are not compatible with the WITH* options */
if (opts->key != NULL && (opts->withcoord || opts->withdist || opts->withhash)) {
php_error_docref(NULL, E_WARNING,
"STORE[DIST] is not compatible with WITHCOORD, WITHDIST or WITHHASH");
if (opts->key) zend_string_release(opts->key);
return FAILURE;
}
/* Success */
return SUCCESS;
}
/* Helper to append options to a GEORADIUS or GEORADIUSBYMEMBER command */
void append_georadius_opts(RedisSock *redis_sock, smart_string *str, short *slot,
geoOptions *opt)
{
char *key;
size_t keylen;
int keyfree;
if (opt->withcoord)
REDIS_CMD_APPEND_SSTR_STATIC(str, "WITHCOORD");
if (opt->withdist)
REDIS_CMD_APPEND_SSTR_STATIC(str, "WITHDIST");
if (opt->withhash)
REDIS_CMD_APPEND_SSTR_STATIC(str, "WITHHASH");
/* Append sort if it's not GEO_NONE */
if (opt->sort == SORT_ASC) {
REDIS_CMD_APPEND_SSTR_STATIC(str, "ASC");
} else if (opt->sort == SORT_DESC) {
REDIS_CMD_APPEND_SSTR_STATIC(str, "DESC");
}
/* Append our count if we've got one */
if (opt->count) {
REDIS_CMD_APPEND_SSTR_STATIC(str, "COUNT");
redis_cmd_append_sstr_long(str, opt->count);
}
/* Append store options if we've got them */
if (opt->store != STORE_NONE && opt->key != NULL) {
/* Grab string bits and prefix if requested */
key = ZSTR_VAL(opt->key);
keylen = ZSTR_LEN(opt->key);
keyfree = redis_key_prefix(redis_sock, &key, &keylen);
if (opt->store == STORE_COORD) {
REDIS_CMD_APPEND_SSTR_STATIC(str, "STORE");
} else {
REDIS_CMD_APPEND_SSTR_STATIC(str, "STOREDIST");
}
redis_cmd_append_sstr(str, key, keylen);
CMD_SET_SLOT(slot, key, keylen);
if (keyfree) free(key);
}
}
/* GEORADIUS / GEORADIUS_RO */
int redis_georadius_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *key, *unit;
short store_slot = 0;
size_t keylen, unitlen;
int argc = 5, keyfree;
double lng, lat, radius;
zval *opts = NULL;
geoOptions gopts = {0};
smart_string cmdstr = {0};
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sddds|a", &key, &keylen,
&lng, &lat, &radius, &unit, &unitlen, &opts)
== FAILURE)
{
return FAILURE;
}
/* Parse any GEORADIUS options we have */
if (opts != NULL) {
/* Attempt to parse our options array */
if (get_georadius_opts(Z_ARRVAL_P(opts), &gopts) != SUCCESS)
{
return FAILURE;
}
}
/* Increment argc depending on options */
argc += gopts.withcoord + gopts.withdist + gopts.withhash +
(gopts.sort != SORT_NONE) + (gopts.count ? 2 : 0) +
(gopts.store != STORE_NONE ? 2 : 0);
/* Begin construction of our command */
redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw));
/* Prefix and set slot */
keyfree = redis_key_prefix(redis_sock, &key, &keylen);
CMD_SET_SLOT(slot, key, keylen);
/* Append required arguments */
redis_cmd_append_sstr(&cmdstr, key, keylen);
redis_cmd_append_sstr_dbl(&cmdstr, lng);
redis_cmd_append_sstr_dbl(&cmdstr, lat);
redis_cmd_append_sstr_dbl(&cmdstr, radius);
redis_cmd_append_sstr(&cmdstr, unit, unitlen);
/* Append optional arguments */
append_georadius_opts(redis_sock, &cmdstr, slot ? &store_slot : NULL, &gopts);
/* Free key if it was prefixed */
if (keyfree) efree(key);
if (gopts.key) zend_string_release(gopts.key);
/* Protect the user from CROSSSLOT if we're in cluster */
if (slot && gopts.store != STORE_NONE && *slot != store_slot) {
php_error_docref(NULL, E_WARNING,
"Key and STORE[DIST] key must hash to the same slot");
efree(cmdstr.c);
return FAILURE;
}
/* Set slot, command and len, and return */
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
/* GEORADIUSBYMEMBER/GEORADIUSBYMEMBER_RO
* key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] */
int redis_georadiusbymember_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *key, *mem, *unit;
size_t keylen, memlen, unitlen;
short store_slot = 0;
int keyfree, argc = 4;
double radius;
geoOptions gopts = {0};
zval *opts = NULL;
smart_string cmdstr = {0};
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssds|a", &key, &keylen,
&mem, &memlen, &radius, &unit, &unitlen, &opts) == FAILURE)
{
return FAILURE;
}
if (opts != NULL) {
/* Attempt to parse our options array */
if (get_georadius_opts(Z_ARRVAL_P(opts), &gopts) == FAILURE) {
return FAILURE;
}
}
/* Increment argc based on options */
argc += gopts.withcoord + gopts.withdist + gopts.withhash +
(gopts.sort != SORT_NONE) + (gopts.count ? 2 : 0) +
(gopts.store != STORE_NONE ? 2 : 0);
/* Begin command construction*/
redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw));
/* Prefix our key if we're prefixing and set the slot */
keyfree = redis_key_prefix(redis_sock, &key, &keylen);
CMD_SET_SLOT(slot, key, keylen);
/* Append required arguments */
redis_cmd_append_sstr(&cmdstr, key, keylen);
redis_cmd_append_sstr(&cmdstr, mem, memlen);
redis_cmd_append_sstr_long(&cmdstr, radius);
redis_cmd_append_sstr(&cmdstr, unit, unitlen);
/* Append options */
append_georadius_opts(redis_sock, &cmdstr, slot ? &store_slot : NULL, &gopts);
/* Free key if we prefixed */
if (keyfree) efree(key);
if (gopts.key) zend_string_release(gopts.key);
/* Protect the user from CROSSSLOT if we're in cluster */
if (slot && gopts.store != STORE_NONE && *slot != store_slot) {
php_error_docref(NULL, E_WARNING,
"Key and STORE[DIST] key must hash to the same slot");
efree(cmdstr.c);
return FAILURE;
}
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
int
redis_geosearch_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key, *unit;
int argc = 2;
short store_slot = 0;
size_t keylen, unitlen;
geoOptions gopts = {0};
smart_string cmdstr = {0};
zval *position, *shape, *opts = NULL, *z_ele;
zend_string *zkey;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "szzs|a",
&key, &keylen, &position, &shape,
&unit, &unitlen, &opts) == FAILURE)
{
return FAILURE;
}
if (Z_TYPE_P(position) == IS_STRING && Z_STRLEN_P(position) > 0) {
argc += 2;
} else if (Z_TYPE_P(position) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL_P(position)) == 2) {
argc += 3;
} else {
php_error_docref(NULL, E_WARNING, "Invalid position");
return FAILURE;
}
if (Z_TYPE_P(shape) == IS_LONG || Z_TYPE_P(shape) == IS_DOUBLE) {
argc += 2;
} else if (Z_TYPE_P(shape) == IS_ARRAY) {
argc += 3;
} else {
php_error_docref(NULL, E_WARNING, "Invalid shape dimensions");
return FAILURE;
}
/* Attempt to parse our options array */
if (opts != NULL && Z_TYPE_P(opts) == IS_ARRAY) {
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(opts), zkey, z_ele) {
ZVAL_DEREF(z_ele);
if (zkey != NULL) {
if (zend_string_equals_literal_ci(zkey, "COUNT")) {
if (Z_TYPE_P(z_ele) != IS_LONG || Z_LVAL_P(z_ele) <= 0) {
php_error_docref(NULL, E_WARNING, "COUNT must be an integer > 0!");
return FAILURE;
}
gopts.count = Z_LVAL_P(z_ele);
}
} else if (Z_TYPE_P(z_ele) == IS_STRING) {
if (!strcasecmp(Z_STRVAL_P(z_ele), "WITHCOORD")) {
gopts.withcoord = 1;
} else if (!strcasecmp(Z_STRVAL_P(z_ele), "WITHDIST")) {
gopts.withdist = 1;
} else if (!strcasecmp(Z_STRVAL_P(z_ele), "WITHHASH")) {
gopts.withhash = 1;
} else if (!strcasecmp(Z_STRVAL_P(z_ele), "ASC")) {
gopts.sort = SORT_ASC;
} else if (!strcasecmp(Z_STRVAL_P(z_ele), "DESC")) {
gopts.sort = SORT_DESC;
}
}
} ZEND_HASH_FOREACH_END();
}
/* Increment argc based on options */
argc += gopts.withcoord + gopts.withdist + gopts.withhash
+ (gopts.sort != SORT_NONE) + (gopts.count ? 2 : 0);
REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "GEOSEARCH");
redis_cmd_append_sstr_key(&cmdstr, key, keylen, redis_sock, slot);
if (Z_TYPE_P(position) == IS_ARRAY) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "FROMLONLAT");
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(position), z_ele) {
ZVAL_DEREF(z_ele);
redis_cmd_append_sstr_dbl(&cmdstr, zval_get_double(z_ele));
} ZEND_HASH_FOREACH_END();
} else {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "FROMMEMBER");
redis_cmd_append_sstr(&cmdstr, Z_STRVAL_P(position), Z_STRLEN_P(position));
}
if (Z_TYPE_P(shape) == IS_ARRAY) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "BYBOX");
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(shape), z_ele) {
ZVAL_DEREF(z_ele);
redis_cmd_append_sstr_dbl(&cmdstr, zval_get_double(z_ele));
} ZEND_HASH_FOREACH_END();
} else {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "BYRADIUS");
redis_cmd_append_sstr_dbl(&cmdstr, zval_get_double(shape));
}
redis_cmd_append_sstr(&cmdstr, unit, unitlen);
/* Append optional arguments */
if (gopts.withcoord) REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "WITHCOORD");
if (gopts.withdist) REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "WITHDIST");
if (gopts.withhash) REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "WITHHASH");
/* Append sort if it's not GEO_NONE */
if (gopts.sort == SORT_ASC) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "ASC");
} else if (gopts.sort == SORT_DESC) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "DESC");
}
/* Append our count if we've got one */
if (gopts.count) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "COUNT");
redis_cmd_append_sstr_long(&cmdstr, gopts.count);
}
if ((argc = gopts.withcoord + gopts.withdist + gopts.withhash) > 0) {
*ctx = PHPREDIS_CTX_PTR;
}
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
int
redis_geosearchstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
int argc = 3;
char *dest, *src, *unit;
short store_slot = 0;
size_t destlen, srclen, unitlen;
geoOptions gopts = {0};
smart_string cmdstr = {0};
zval *position, *shape, *opts = NULL, *z_ele;
zend_string *zkey;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sszzs|a",
&dest, &destlen, &src, &srclen, &position, &shape,
&unit, &unitlen, &opts) == FAILURE)
{
return FAILURE;
}
if (Z_TYPE_P(position) == IS_STRING && Z_STRLEN_P(position) > 0) {
argc += 2;
} else if (Z_TYPE_P(position) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL_P(position)) == 2) {
argc += 3;
} else {
php_error_docref(NULL, E_WARNING, "Invalid position");
return FAILURE;
}
if (Z_TYPE_P(shape) == IS_LONG || Z_TYPE_P(shape) == IS_DOUBLE) {
argc += 2;
} else if (Z_TYPE_P(shape) == IS_ARRAY) {
argc += 3;
} else {
php_error_docref(NULL, E_WARNING, "Invalid shape dimensions");
return FAILURE;
}
/* Attempt to parse our options array */
if (opts != NULL && Z_TYPE_P(opts) == IS_ARRAY) {
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(opts), zkey, z_ele) {
ZVAL_DEREF(z_ele);
if (zkey != NULL) {
if (zend_string_equals_literal_ci(zkey, "COUNT")) {
if (Z_TYPE_P(z_ele) != IS_LONG || Z_LVAL_P(z_ele) <= 0) {
php_error_docref(NULL, E_WARNING, "COUNT must be an integer > 0!");
return FAILURE;
}
gopts.count = Z_LVAL_P(z_ele);
}
} else if (Z_TYPE_P(z_ele) == IS_STRING) {
if (!strcasecmp(Z_STRVAL_P(z_ele), "ASC")) {
gopts.sort = SORT_ASC;
} else if (!strcasecmp(Z_STRVAL_P(z_ele), "DESC")) {
gopts.sort = SORT_DESC;
} else if (!strcasecmp(Z_STRVAL_P(z_ele), "STOREDIST")) {
gopts.store = STORE_DIST;
}
}
} ZEND_HASH_FOREACH_END();
}
/* Increment argc based on options */
argc += gopts.withcoord + gopts.withdist + gopts.withhash
+ (gopts.sort != SORT_NONE) + (gopts.count ? 2 : 0)
+ (gopts.store != STORE_NONE);
REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "GEOSEARCHSTORE");
redis_cmd_append_sstr_key(&cmdstr, dest, destlen, redis_sock, slot);
redis_cmd_append_sstr_key(&cmdstr, src, srclen, redis_sock, slot);
if (Z_TYPE_P(position) == IS_ARRAY) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "FROMLONLAT");
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(position), z_ele) {
ZVAL_DEREF(z_ele);
redis_cmd_append_sstr_dbl(&cmdstr, zval_get_double(z_ele));
} ZEND_HASH_FOREACH_END();
} else {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "FROMMEMBER");
redis_cmd_append_sstr(&cmdstr, Z_STRVAL_P(position), Z_STRLEN_P(position));
}
if (Z_TYPE_P(shape) == IS_ARRAY) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "BYBOX");
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(shape), z_ele) {
ZVAL_DEREF(z_ele);
redis_cmd_append_sstr_dbl(&cmdstr, zval_get_double(z_ele));
} ZEND_HASH_FOREACH_END();
} else {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "BYRADIUS");
redis_cmd_append_sstr_dbl(&cmdstr, zval_get_double(shape));
}
redis_cmd_append_sstr(&cmdstr, unit, unitlen);
/* Append sort if it's not GEO_NONE */
if (gopts.sort == SORT_ASC) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "ASC");
} else if (gopts.sort == SORT_DESC) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "DESC");
}
/* Append our count if we've got one */
if (gopts.count) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "COUNT");
redis_cmd_append_sstr_long(&cmdstr, gopts.count);
}
if (gopts.store == STORE_DIST) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "STOREDIST");
}
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
/* MIGRATE */
int redis_migrate_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
smart_string cmdstr = {0};
char *host, *key;
int argc, keyfree;
zval *z_keys, *z_key;
size_t hostlen, keylen;
zend_long destdb, port, timeout;
zend_bool copy = 0, replace = 0;
zend_string *zstr;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "slzll|bb", &host, &hostlen, &port,
&z_keys, &destdb, &timeout, &copy, &replace) == FAILURE)
{
return FAILURE;
}
/* Protect against being passed an array with zero elements */
if (Z_TYPE_P(z_keys) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL_P(z_keys)) == 0) {
php_error_docref(NULL, E_WARNING, "Keys array cannot be empty");
return FAILURE;
}
/* host, port, key|"", dest-db, timeout, [copy, replace] [KEYS key1..keyN] */
argc = 5 + copy + replace;
if (Z_TYPE_P(z_keys) == IS_ARRAY) {
/* +1 for the "KEYS" argument itself */
argc += 1 + zend_hash_num_elements(Z_ARRVAL_P(z_keys));
}
/* Initialize MIGRATE command with host and port */
REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "MIGRATE");
redis_cmd_append_sstr(&cmdstr, host, hostlen);
redis_cmd_append_sstr_long(&cmdstr, port);
/* If passed a keys array the keys come later, otherwise pass the key to
* migrate here */
if (Z_TYPE_P(z_keys) == IS_ARRAY) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "");
} else {
/* Grab passed value as a string */
zstr = zval_get_string(z_keys);
/* We may need to prefix our string */
key = ZSTR_VAL(zstr);
keylen = ZSTR_LEN(zstr);
keyfree = redis_key_prefix(redis_sock, &key, &keylen);
/* Add key to migrate */
redis_cmd_append_sstr(&cmdstr, key, keylen);
zend_string_release(zstr);
if (keyfree) efree(key);
}
redis_cmd_append_sstr_long(&cmdstr, destdb);
redis_cmd_append_sstr_long(&cmdstr, timeout);
REDIS_CMD_APPEND_SSTR_OPT_STATIC(&cmdstr, copy, "COPY");
REDIS_CMD_APPEND_SSTR_OPT_STATIC(&cmdstr, replace, "REPLACE");
/* Append actual keys if we've got a keys array */
if (Z_TYPE_P(z_keys) == IS_ARRAY) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "KEYS");
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z_keys), z_key) {
zstr = zval_get_string(z_key);
key = ZSTR_VAL(zstr);
keylen = ZSTR_LEN(zstr);
keyfree = redis_key_prefix(redis_sock, &key, &keylen);
/* Append the key */
redis_cmd_append_sstr(&cmdstr, key, keylen);
zend_string_release(zstr);
if (keyfree) efree(key);
} ZEND_HASH_FOREACH_END();
}
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
/* EXISTS */
int redis_exists_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
"EXISTS", sizeof("EXISTS") - 1, 0, 0, cmd, cmd_len, slot);
}
/* DEL */
int redis_del_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
"DEL", sizeof("DEL")-1, 1, 0, cmd, cmd_len, slot);
}
/* UNLINK */
int redis_unlink_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
"UNLINK", sizeof("UNLINK")-1, 1, 0, cmd, cmd_len, slot);
}
/* WATCH */
int redis_watch_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
"WATCH", sizeof("WATCH")-1, 1, 0, cmd, cmd_len, slot);
}
/* SINTER */
int redis_sinter_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
"SINTER", sizeof("SINTER")-1, 1, 0, cmd, cmd_len, slot);
}
/* SINTERSTORE */
int redis_sinterstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
"SINTERSTORE", sizeof("SINTERSTORE")-1, 1, 0, cmd, cmd_len, slot);
}
/* SUNION */
int redis_sunion_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
"SUNION", sizeof("SUNION")-1, 1, 0, cmd, cmd_len, slot);
}
/* SUNIONSTORE */
int redis_sunionstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
"SUNIONSTORE", sizeof("SUNIONSTORE")-1, 2, 0, cmd, cmd_len, slot);
}
/* SDIFF */
int redis_sdiff_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, "SDIFF",
sizeof("SDIFF")-1, 1, 0, cmd, cmd_len, slot);
}
/* SDIFFSTORE */
int redis_sdiffstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
"SDIFFSTORE", sizeof("SDIFFSTORE")-1, 1, 0, cmd, cmd_len, slot);
}
/* COMMAND */
int redis_command_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *kw=NULL;
zval *z_arg;
size_t kw_len;
/* Parse our args */
if (zend_parse_parameters(ZEND_NUM_ARGS(), "|sz", &kw, &kw_len,
&z_arg) == FAILURE)
{
return FAILURE;
}
/* Construct our command */
if (!kw) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "COMMAND", "");
} else if (!z_arg) {
/* Sanity check */
if (strncasecmp(kw, "count", sizeof("count") - 1)) {
return FAILURE;
}
/* COMMAND COUNT */
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "COMMAND", "s", "COUNT", sizeof("COUNT") - 1);
} else if (Z_TYPE_P(z_arg) == IS_STRING) {
/* Sanity check */
if (strncasecmp(kw, "info", sizeof("info") - 1)) {
return FAILURE;
}
/* COMMAND INFO <cmd> */
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "COMMAND", "ss", "INFO", sizeof("INFO") - 1,
Z_STRVAL_P(z_arg), Z_STRLEN_P(z_arg));
} else {
int arr_len;
/* Sanity check on args */
if (strncasecmp(kw, "getkeys", sizeof("getkeys")-1) ||
Z_TYPE_P(z_arg)!=IS_ARRAY ||
(arr_len=zend_hash_num_elements(Z_ARRVAL_P(z_arg)))<1)
{
return FAILURE;
}
zval *z_ele;
HashTable *ht_arr = Z_ARRVAL_P(z_arg);
smart_string cmdstr = {0};
redis_cmd_init_sstr(&cmdstr, 1 + arr_len, "COMMAND", sizeof("COMMAND")-1);
redis_cmd_append_sstr(&cmdstr, "GETKEYS", sizeof("GETKEYS")-1);
ZEND_HASH_FOREACH_VAL(ht_arr, z_ele) {
zend_string *zstr = zval_get_string(z_ele);
redis_cmd_append_sstr(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
zend_string_release(zstr);
} ZEND_HASH_FOREACH_END();
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
}
/* Any slot will do */
CMD_RAND_SLOT(slot);
return SUCCESS;
}
int
redis_copy_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
smart_string cmdstr = {0};
char *src, *dst;
size_t src_len, dst_len;
zend_long db = -1;
zend_bool replace = 0;
zval *opts = NULL;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|a",
&src, &src_len, &dst, &dst_len, &opts) == FAILURE)
{
return FAILURE;
}
if (opts != NULL && Z_TYPE_P(opts) == IS_ARRAY) {
zend_string *zkey;
zval *z_ele;
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(opts), zkey, z_ele) {
if (zkey != NULL) {
ZVAL_DEREF(z_ele);
if (zend_string_equals_literal_ci(zkey, "db")) {
db = zval_get_long(z_ele);
} else if (zend_string_equals_literal_ci(zkey, "replace")) {
replace = zval_is_true(z_ele);
}
}
} ZEND_HASH_FOREACH_END();
}
REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, 2 + (db > -1) + replace, "COPY");
redis_cmd_append_sstr(&cmdstr, src, src_len);
redis_cmd_append_sstr(&cmdstr, dst, dst_len);
if (db > -1) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "DB");
redis_cmd_append_sstr_long(&cmdstr, db);
}
if (replace) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "REPLACE");
}
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
/* XADD */
int redis_xadd_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
smart_string cmdstr = {0};
zend_string *arrkey;
zval *z_fields, *value;
zend_long maxlen = 0;
zend_bool approx = 0;
zend_ulong idx;
HashTable *ht_fields;
int fcount, argc;
char *key, *id;
size_t keylen, idlen;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssa|lb", &key, &keylen,
&id, &idlen, &z_fields, &maxlen, &approx) == FAILURE)
{
return FAILURE;
}
/* At least one field and string are required */
ht_fields = Z_ARRVAL_P(z_fields);
if ((fcount = zend_hash_num_elements(ht_fields)) == 0) {
return FAILURE;
}
if (maxlen < 0 || (maxlen == 0 && approx != 0)) {
php_error_docref(NULL, E_WARNING,
"Warning: Invalid MAXLEN argument or approximate flag");
}
/* Calculate argc for XADD. It's a bit complex because we've got
* an optional MAXLEN argument which can either take the form MAXLEN N
* or MAXLEN ~ N */
argc = 2 + (fcount*2) + (maxlen > 0 ? (approx ? 3 : 2) : 0);
/* XADD key ID field string [field string ...] */
REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "XADD");
redis_cmd_append_sstr_key(&cmdstr, key, keylen, redis_sock, slot);
/* Now append our MAXLEN bits if we've got them */
if (maxlen > 0) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "MAXLEN");
REDIS_CMD_APPEND_SSTR_OPT_STATIC(&cmdstr, approx, "~");
redis_cmd_append_sstr_long(&cmdstr, maxlen);
}
/* Now append ID and field(s) */
redis_cmd_append_sstr(&cmdstr, id, idlen);
ZEND_HASH_FOREACH_KEY_VAL(ht_fields, idx, arrkey, value) {
redis_cmd_append_sstr_arrkey(&cmdstr, arrkey, idx);
redis_cmd_append_sstr_zval(&cmdstr, value, redis_sock);
} ZEND_HASH_FOREACH_END();
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
// XPENDING key group [start end count [consumer] [idle]]
int redis_xpending_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
smart_string cmdstr = {0};
char *key, *group, *start = NULL, *end = NULL, *consumer = NULL;
size_t keylen, grouplen, startlen, endlen, consumerlen;
int argc;
zend_long count = -1, idle = 0;
// XPENDING mystream group55 - + 10 consumer-123
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|sslsl", &key,
&keylen, &group, &grouplen, &start, &startlen,
&end, &endlen, &count, &consumer, &consumerlen,
&idle) == FAILURE)
{
return FAILURE;
}
/* If we've been passed a start argument, we also need end and count */
if (start != NULL && (end == NULL || count < 0)) {
return FAILURE;
}
/* Calculate argc. It's either 2, 5, 6 or 7 */
argc = 2 + (start != NULL ? 3 + (consumer != NULL) + (idle != 0) : 0);
/* Construct command and add required arguments */
REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "XPENDING");
redis_cmd_append_sstr_key(&cmdstr, key, keylen, redis_sock, slot);
redis_cmd_append_sstr(&cmdstr, group, grouplen);
/* Add optional argumentst */
if (start) {
if (idle != 0) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "IDLE");
redis_cmd_append_sstr_long(&cmdstr, (long)idle);
}
redis_cmd_append_sstr(&cmdstr, start, startlen);
redis_cmd_append_sstr(&cmdstr, end, endlen);
redis_cmd_append_sstr_long(&cmdstr, (long)count);
/* Finally add consumer if we have it */
if (consumer) redis_cmd_append_sstr(&cmdstr, consumer, consumerlen);
}
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
/* X[REV]RANGE key start end [COUNT count] */
int redis_xrange_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
smart_string cmdstr = {0};
char *key, *start, *end;
size_t keylen, startlen, endlen;
zend_long count = -1;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|l", &key, &keylen,
&start, &startlen, &end, &endlen, &count)
== FAILURE)
{
return FAILURE;
}
redis_cmd_init_sstr(&cmdstr, 3 + (2 * (count > -1)), kw, strlen(kw));
redis_cmd_append_sstr_key(&cmdstr, key, keylen, redis_sock, slot);
redis_cmd_append_sstr(&cmdstr, start, startlen);
redis_cmd_append_sstr(&cmdstr, end, endlen);
if (count > -1) {
redis_cmd_append_sstr(&cmdstr, "COUNT", sizeof("COUNT")-1);
redis_cmd_append_sstr_long(&cmdstr, count);
}
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
/* Helper function to take an associative array and append the Redis
* STREAMS stream [stream...] id [id ...] arguments to a command string. */
static int
append_stream_args(smart_string *cmdstr, HashTable *ht, RedisSock *redis_sock,
short *slot)
{
char *kptr, kbuf[40];
int klen, i, pos = 0;
zend_string *key, *idstr;
short oldslot;
zval **id;
zend_ulong idx;
/* Append STREAM qualifier */
REDIS_CMD_APPEND_SSTR_STATIC(cmdstr, "STREAMS");
/* Sentinel slot */
if (slot) oldslot = -1;
/* Allocate memory to keep IDs */
id = emalloc(sizeof(*id) * zend_hash_num_elements(ht));
/* Iterate over our stream => id array appending streams and retaining each
* value for final arguments */
ZEND_HASH_FOREACH_KEY_VAL(ht, idx, key, id[pos++]) {
if (key) {
klen = ZSTR_LEN(key);
kptr = ZSTR_VAL(key);
} else {
klen = snprintf(kbuf, sizeof(kbuf), "%ld", (long)idx);
kptr = (char*)kbuf;
}
/* Append stream key */
redis_cmd_append_sstr_key(cmdstr, kptr, klen, redis_sock, slot);
/* Protect the user against CROSSSLOT to avoid confusion */
if (slot) {
if (oldslot != -1 && *slot != oldslot) {
php_error_docref(NULL, E_WARNING,
"Warning, not all keys hash to the same slot!");
efree(id);
return FAILURE;
}
oldslot = *slot;
}
} ZEND_HASH_FOREACH_END();
/* Add our IDs */
for (i = 0; i < pos; i++) {
idstr = zval_get_string(id[i]);
redis_cmd_append_sstr(cmdstr, ZSTR_VAL(idstr), ZSTR_LEN(idstr));
zend_string_release(idstr);
}
/* Clean up ID container array */
efree(id);
return 0;
}
/* XREAD [COUNT count] [BLOCK ms] STREAMS key [key ...] id [id ...] */
int redis_xread_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
smart_string cmdstr = {0};
zend_long count = -1, block = -1;
zval *z_streams;
int argc, scount;
HashTable *kt;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "a|ll", &z_streams,
&count, &block) == FAILURE)
{
return FAILURE;
}
/* At least one stream and ID is required */
kt = Z_ARRVAL_P(z_streams);
if ((scount = zend_hash_num_elements(kt)) < 1) {
return FAILURE;
}
/* Calculate argc and start constructing command */
argc = 1 + (2 * scount) + (2 * (count > -1)) + (2 * (block > -1));
REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "XREAD");
/* Append COUNT if we have it */
if (count > -1) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "COUNT");
redis_cmd_append_sstr_long(&cmdstr, count);
}
/* Append BLOCK if we have it */
if (block > -1) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "BLOCK");
redis_cmd_append_sstr_long(&cmdstr, block);
}
/* Append final STREAM key [key ...] id [id ...] arguments */
if (append_stream_args(&cmdstr, kt, redis_sock, slot) < 0) {
efree(cmdstr.c);
return FAILURE;
}
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
/* XREADGROUP GROUP group consumer [COUNT count] [BLOCK ms]
* STREAMS key [key ...] id [id ...] */
int redis_xreadgroup_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
smart_string cmdstr = {0};
zval *z_streams;
HashTable *kt;
char *group, *consumer;
size_t grouplen, consumerlen;
int scount, argc;
zend_long count, block;
zend_bool no_count = 1, no_block = 1;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssa|l!l!", &group,
&grouplen, &consumer, &consumerlen, &z_streams,
&count, &no_count, &block, &no_block) == FAILURE)
{
return FAILURE;
}
/* Negative COUNT or BLOCK is illegal so abort immediately */
if ((!no_count && count < 0) || (!no_block && block < 0)) {
php_error_docref(NULL, E_WARNING, "Negative values for COUNT or BLOCK are illegal.");
return FAILURE;
}
/* Redis requires at least one stream */
kt = Z_ARRVAL_P(z_streams);
if ((scount = zend_hash_num_elements(kt)) < 1) {
return FAILURE;
}
/* Calculate argc and start constructing commands */
argc = 4 + (2 * scount) + (2 * !no_count) + (2 * !no_block);
REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "XREADGROUP");
/* Group and consumer */
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "GROUP");
redis_cmd_append_sstr(&cmdstr, group, grouplen);
redis_cmd_append_sstr(&cmdstr, consumer, consumerlen);
/* Append COUNT if we have it */
if (!no_count) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "COUNT");
redis_cmd_append_sstr_long(&cmdstr, count);
}
/* Append BLOCK argument if we have it */
if (!no_block) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "BLOCK");
redis_cmd_append_sstr_long(&cmdstr, block);
}
/* Finally append stream and id args */
if (append_stream_args(&cmdstr, kt, redis_sock, slot) < 0) {
efree(cmdstr.c);
return FAILURE;
}
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
/* XACK key group id [id ...] */
int redis_xack_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
smart_string cmdstr = {0};
char *key, *group;
size_t keylen, grouplen;
zend_string *idstr;
zval *z_ids, *z_id;
HashTable *ht_ids;
int idcount;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssa", &key, &keylen,
&group, &grouplen, &z_ids) == FAILURE)
{
return FAILURE;
}
ht_ids = Z_ARRVAL_P(z_ids);
if ((idcount = zend_hash_num_elements(ht_ids)) < 1) {
return FAILURE;
}
/* Create command and add initial arguments */
REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, 2 + idcount, "XACK");
redis_cmd_append_sstr_key(&cmdstr, key, keylen, redis_sock, slot);
redis_cmd_append_sstr(&cmdstr, group, grouplen);
/* Append IDs */
ZEND_HASH_FOREACH_VAL(ht_ids, z_id) {
idstr = zval_get_string(z_id);
redis_cmd_append_sstr(&cmdstr, ZSTR_VAL(idstr), ZSTR_LEN(idstr));
zend_string_release(idstr);
} ZEND_HASH_FOREACH_END();
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
/* XCLAIM options container */
typedef struct xclaimOptions {
struct {
char *type;
int64_t time;
} idle;
zend_long retrycount;
int force;
int justid;
} xclaimOptions;
/* Attempt to extract an int64_t from the provided zval */
static int zval_get_i64(zval *zv, int64_t *retval) {
if (Z_TYPE_P(zv) == IS_LONG) {
*retval = (int64_t)Z_LVAL_P(zv);
return SUCCESS;
} else if (Z_TYPE_P(zv) == IS_DOUBLE) {
*retval = (int64_t)Z_DVAL_P(zv);
return SUCCESS;
} else if (Z_TYPE_P(zv) == IS_STRING) {
zend_long lval;
double dval;
switch (is_numeric_string(Z_STRVAL_P(zv), Z_STRLEN_P(zv), &lval, &dval, 1)) {
case IS_LONG:
*retval = (int64_t)lval;
return SUCCESS;
case IS_DOUBLE:
*retval = (int64_t)dval;
return SUCCESS;
}
}
/* If we make it here we have failed */
return FAILURE;
}
/* Helper function to get an integer XCLAIM argument. This can overflow a
* 32-bit PHP long so we have to extract it as an int64_t. If the value is
* not a valid number or negative, we'll inform the user of the problem and
* that the argument is being ignored. */
static int64_t get_xclaim_i64_arg(const char *key, zval *zv) {
int64_t retval = -1;
/* Extract an i64, and if we can't let the user know there is an issue. */
if (zval_get_i64(zv, &retval) == FAILURE || retval < 0) {
php_error_docref(NULL, E_WARNING,
"Invalid XCLAIM option '%s' will be ignored", key);
}
return retval;
}
/* Helper to extract XCLAIM options */
static void get_xclaim_options(zval *z_arr, xclaimOptions *opt) {
HashTable *ht;
zend_string *zkey;
char *kval;
size_t klen;
zval *zv;
/* Initialize options array to sane defaults */
memset(opt, 0, sizeof(*opt));
opt->retrycount = -1;
opt->idle.time = -1;
/* Early return if we don't have any options */
if (z_arr == NULL)
return;
/* Iterate over our options array */
ht = Z_ARRVAL_P(z_arr);
ZEND_HASH_FOREACH_STR_KEY_VAL(ht, zkey, zv) {
if (zkey) {
kval = ZSTR_VAL(zkey);
klen = ZSTR_LEN(zkey);
if (klen == 4) {
if (!strncasecmp(kval, "TIME", 4)) {
opt->idle.type = "TIME";
opt->idle.time = get_xclaim_i64_arg("TIME", zv);
} else if (!strncasecmp(kval, "IDLE", 4)) {
opt->idle.type = "IDLE";
opt->idle.time = get_xclaim_i64_arg("IDLE", zv);
}
} else if (klen == 10 && !strncasecmp(kval, "RETRYCOUNT", 10)) {
opt->retrycount = zval_get_long(zv);
}
} else {
if (Z_TYPE_P(zv) == IS_STRING) {
kval = Z_STRVAL_P(zv);
klen = Z_STRLEN_P(zv);
if (klen == 5 && !strncasecmp(kval, "FORCE", 5)) {
opt->force = 1;
} else if (klen == 6 && !strncasecmp(kval, "JUSTID", 6)) {
opt->justid = 1;
}
}
}
} ZEND_HASH_FOREACH_END();
}
/* Count argc for any options we may have */
static int xclaim_options_argc(xclaimOptions *opt) {
int argc = 0;
if (opt->idle.type != NULL && opt->idle.time != -1)
argc += 2;
if (opt->retrycount != -1)
argc += 2;
if (opt->force)
argc++;
if (opt->justid)
argc++;
return argc;
}
/* Append XCLAIM options */
static void append_xclaim_options(smart_string *cmd, xclaimOptions *opt) {
/* IDLE/TIME long */
if (opt->idle.type != NULL && opt->idle.time != -1) {
redis_cmd_append_sstr(cmd, opt->idle.type, strlen(opt->idle.type));
redis_cmd_append_sstr_i64(cmd, opt->idle.time);
}
/* RETRYCOUNT */
if (opt->retrycount != -1) {
REDIS_CMD_APPEND_SSTR_STATIC(cmd, "RETRYCOUNT");
redis_cmd_append_sstr_long(cmd, opt->retrycount);
}
/* FORCE and JUSTID */
if (opt->force)
REDIS_CMD_APPEND_SSTR_STATIC(cmd, "FORCE");
if (opt->justid)
REDIS_CMD_APPEND_SSTR_STATIC(cmd, "JUSTID");
}
/* XCLAIM <key> <group> <consumer> <min-idle-time> <ID-1> <ID-2>
[IDLE <milliseconds>] [TIME <mstime>] [RETRYCOUNT <count>]
[FORCE] [JUSTID] */
int redis_xclaim_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
smart_string cmdstr = {0};
char *key, *group, *consumer;
size_t keylen, grouplen, consumerlen;
zend_long min_idle;
int argc, id_count;
zval *z_ids, *z_id, *z_opts = NULL;
zend_string *zstr;
HashTable *ht_ids;
xclaimOptions opts;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sssla|a", &key, &keylen,
&group, &grouplen, &consumer, &consumerlen, &min_idle,
&z_ids, &z_opts) == FAILURE)
{
return FAILURE;
}
/* At least one id is required */
ht_ids = Z_ARRVAL_P(z_ids);
if ((id_count = zend_hash_num_elements(ht_ids)) < 1) {
return FAILURE;
}
/* Extract options array if we've got them */
get_xclaim_options(z_opts, &opts);
/* Now we have enough information to calculate argc */
argc = 4 + id_count + xclaim_options_argc(&opts);
/* Start constructing our command */
REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "XCLAIM");
redis_cmd_append_sstr_key(&cmdstr, key, keylen, redis_sock, slot);
redis_cmd_append_sstr(&cmdstr, group, grouplen);
redis_cmd_append_sstr(&cmdstr, consumer, consumerlen);
redis_cmd_append_sstr_long(&cmdstr, min_idle);
/* Add IDs */
ZEND_HASH_FOREACH_VAL(ht_ids, z_id) {
zstr = zval_get_string(z_id);
redis_cmd_append_sstr(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
zend_string_release(zstr);
} ZEND_HASH_FOREACH_END();
/* Finally add our options */
append_xclaim_options(&cmdstr, &opts);
/* Success */
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
/* XGROUP HELP
* XGROUP CREATE key groupname id [MKSTREAM]
* XGROUP SETID key group id
* XGROUP DESTROY key groupname
* XGROUP DELCONSUMER key groupname consumername */
int redis_xgroup_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *op, *key = NULL, *arg1 = NULL, *arg2 = NULL;
size_t oplen, keylen, arg1len, arg2len;
zend_bool mkstream = 0;
int argc = ZEND_NUM_ARGS();
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|sssb", &op, &oplen,
&key, &keylen, &arg1, &arg1len, &arg2, &arg2len,
&mkstream) == FAILURE)
{
return FAILURE;
}
if (argc == 1 && oplen == 4 && !strncasecmp(op, "HELP", 4)) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "XGROUP", "s", "HELP", 4);
return SUCCESS;
} else if (argc >= 4 && (oplen == 6 && !strncasecmp(op, "CREATE", 6))) {
if (mkstream) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "XGROUP", "sksss", op, oplen, key, keylen,
arg1, arg1len, arg2, arg2len, "MKSTREAM",
sizeof("MKSTREAM") - 1);
} else {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "XGROUP", "skss", op, oplen, key, keylen,
arg1, arg1len, arg2, arg2len);
}
return SUCCESS;
} else if (argc == 4 && ((oplen == 5 && !strncasecmp(op, "SETID", 5)) ||
(oplen == 11 && !strncasecmp(op, "DELCONSUMER", 11))))
{
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "XGROUP", "skss", op, oplen, key, keylen,
arg1, arg1len, arg2, arg2len);
return SUCCESS;
} else if (argc == 3 && ((oplen == 7 && !strncasecmp(op, "DESTROY", 7)))) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "XGROUP", "sks", op, oplen, key,
keylen, arg1, arg1len);
return SUCCESS;
}
/* Didn't detect any valid XGROUP command pattern */
return FAILURE;
}
/* XINFO CONSUMERS key group
* XINFO GROUPS key
* XINFO STREAM key [FULL [COUNT N]]
* XINFO HELP */
int redis_xinfo_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *op, *key, *arg = NULL;
size_t oplen, keylen, arglen;
zend_long count = -1;
int argc = ZEND_NUM_ARGS();
char fmt[] = "skssl";
if (argc > 4 || zend_parse_parameters(ZEND_NUM_ARGS(), "s|ssl",
&op, &oplen, &key, &keylen, &arg,
&arglen, &count) == FAILURE)
{
return FAILURE;
}
/* Handle everything except XINFO STREAM */
if (strncasecmp(op, "STREAM", 6) != 0) {
fmt[argc] = '\0';
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "XINFO", fmt, op, oplen, key, keylen,
arg, arglen);
return SUCCESS;
}
/* 'FULL' is the only legal option to XINFO STREAM */
if (argc > 2 && strncasecmp(arg, "FULL", 4) != 0) {
php_error_docref(NULL, E_WARNING, "'%s' is not a valid option for XINFO STREAM", arg);
return FAILURE;
}
/* If we have a COUNT bump the argument count to account for the 'COUNT' literal */
if (argc == 4) argc++;
fmt[argc] = '\0';
/* Build our XINFO STREAM variant */
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "XINFO", fmt, "STREAM", 6, key, keylen,
"FULL", 4, "COUNT", 5, count);
return SUCCESS;
}
/* XTRIM MAXLEN [~] count */
int redis_xtrim_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key;
size_t keylen;
zend_long maxlen;
zend_bool approx = 0;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sl|b", &key, &keylen,
&maxlen, &approx) == FAILURE)
{
return FAILURE;
}
if (approx) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "XTRIM", "kssl", key, keylen,
"MAXLEN", 6, "~", 1, maxlen);
} else {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "XTRIM", "ksl", key, keylen,
"MAXLEN", 6, maxlen);
}
return SUCCESS;
}
int
redis_sentinel_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot, void **ctx)
{
if (zend_parse_parameters_none() == FAILURE) {
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "SENTINEL", "s", kw, strlen(kw));
return SUCCESS;
}
int
redis_sentinel_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot, void **ctx)
{
zend_string *name;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &name) == FAILURE) {
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "SENTINEL", "sS", kw, strlen(kw), name);
return SUCCESS;
}
/*
* Redis commands that don't deal with the server at all. The RedisSock*
* pointer is the only thing retrieved differently, so we just take that
* in addition to the standard INTERNAL_FUNCTION_PARAMETERS for arg parsing,
* return value handling, and thread safety. */
void redis_getoption_handler(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock, redisCluster *c)
{
zend_long option;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &option)
== FAILURE)
{
RETURN_FALSE;
}
// Return the requested option
switch(option) {
case REDIS_OPT_SERIALIZER:
RETURN_LONG(redis_sock->serializer);
case REDIS_OPT_COMPRESSION:
RETURN_LONG(redis_sock->compression);
case REDIS_OPT_COMPRESSION_LEVEL:
RETURN_LONG(redis_sock->compression_level);
case REDIS_OPT_PREFIX:
if (redis_sock->prefix) {
RETURN_STRINGL(ZSTR_VAL(redis_sock->prefix), ZSTR_LEN(redis_sock->prefix));
}
RETURN_NULL();
case REDIS_OPT_READ_TIMEOUT:
RETURN_DOUBLE(redis_sock->read_timeout);
case REDIS_OPT_TCP_KEEPALIVE:
RETURN_LONG(redis_sock->tcp_keepalive);
case REDIS_OPT_SCAN:
RETURN_LONG(redis_sock->scan);
case REDIS_OPT_REPLY_LITERAL:
RETURN_LONG(redis_sock->reply_literal);
case REDIS_OPT_NULL_MBULK_AS_NULL:
RETURN_LONG(redis_sock->null_mbulk_as_null);
case REDIS_OPT_FAILOVER:
RETURN_LONG(c->failover);
case REDIS_OPT_MAX_RETRIES:
RETURN_LONG(redis_sock->max_retries);
case REDIS_OPT_BACKOFF_ALGORITHM:
RETURN_LONG(redis_sock->backoff.algorithm);
case REDIS_OPT_BACKOFF_BASE:
RETURN_LONG(redis_sock->backoff.base / 1000);
case REDIS_OPT_BACKOFF_CAP:
RETURN_LONG(redis_sock->backoff.cap / 1000);
default:
RETURN_FALSE;
}
}
void redis_setoption_handler(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock, redisCluster *c)
{
zend_long val_long, option;
zval *val;
zend_string *val_str;
struct timeval read_tv;
int tcp_keepalive = 0;
php_netstream_data_t *sock;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "lz", &option,
&val) == FAILURE)
{
RETURN_FALSE;
}
switch(option) {
case REDIS_OPT_SERIALIZER:
val_long = zval_get_long(val);
if (val_long == REDIS_SERIALIZER_NONE
|| val_long == REDIS_SERIALIZER_PHP
|| val_long == REDIS_SERIALIZER_JSON
#ifdef HAVE_REDIS_IGBINARY
|| val_long == REDIS_SERIALIZER_IGBINARY
#endif
#ifdef HAVE_REDIS_MSGPACK
|| val_long == REDIS_SERIALIZER_MSGPACK
#endif
) {
redis_sock->serializer = val_long;
RETURN_TRUE;
}
break;
case REDIS_OPT_REPLY_LITERAL:
val_long = zval_get_long(val);
redis_sock->reply_literal = val_long != 0;
RETURN_TRUE;
case REDIS_OPT_NULL_MBULK_AS_NULL:
val_long = zval_get_long(val);
redis_sock->null_mbulk_as_null = val_long != 0;
RETURN_TRUE;
case REDIS_OPT_COMPRESSION:
val_long = zval_get_long(val);
if (val_long == REDIS_COMPRESSION_NONE
#ifdef HAVE_REDIS_LZF
|| val_long == REDIS_COMPRESSION_LZF
#endif
#ifdef HAVE_REDIS_ZSTD
|| val_long == REDIS_COMPRESSION_ZSTD
#endif
#ifdef HAVE_REDIS_LZ4
|| val_long == REDIS_COMPRESSION_LZ4
#endif
) {
redis_sock->compression = val_long;
RETURN_TRUE;
}
break;
case REDIS_OPT_COMPRESSION_LEVEL:
val_long = zval_get_long(val);
redis_sock->compression_level = val_long;
RETURN_TRUE;
case REDIS_OPT_PREFIX:
if (redis_sock->prefix) {
zend_string_release(redis_sock->prefix);
redis_sock->prefix = NULL;
}
val_str = zval_get_string(val);
if (ZSTR_LEN(val_str) > 0) {
redis_sock->prefix = val_str;
} else {
zend_string_release(val_str);
}
RETURN_TRUE;
case REDIS_OPT_READ_TIMEOUT:
redis_sock->read_timeout = zval_get_double(val);
if (redis_sock->stream) {
read_tv.tv_sec = (time_t)redis_sock->read_timeout;
read_tv.tv_usec = (int)((redis_sock->read_timeout -
read_tv.tv_sec) * 1000000);
php_stream_set_option(redis_sock->stream,
PHP_STREAM_OPTION_READ_TIMEOUT, 0,
&read_tv);
}
RETURN_TRUE;
case REDIS_OPT_TCP_KEEPALIVE:
/* Don't set TCP_KEEPALIVE if we're using a unix socket. */
if (ZSTR_VAL(redis_sock->host)[0] == '/' && redis_sock->port < 1) {
RETURN_FALSE;
}
tcp_keepalive = zval_get_long(val) > 0 ? 1 : 0;
if (redis_sock->tcp_keepalive == tcp_keepalive) {
RETURN_TRUE;
}
if (redis_sock->stream) {
/* set TCP_KEEPALIVE */
sock = (php_netstream_data_t*)redis_sock->stream->abstract;
if (setsockopt(sock->socket, SOL_SOCKET, SO_KEEPALIVE, (char*)&tcp_keepalive,
sizeof(tcp_keepalive)) == -1) {
RETURN_FALSE;
}
redis_sock->tcp_keepalive = tcp_keepalive;
}
RETURN_TRUE;
case REDIS_OPT_SCAN:
val_long = zval_get_long(val);
if (val_long == REDIS_SCAN_NORETRY) {
redis_sock->scan &= ~REDIS_SCAN_RETRY;
} else if (val_long == REDIS_SCAN_NOPREFIX) {
redis_sock->scan &= ~REDIS_SCAN_PREFIX;
} else if (val_long == REDIS_SCAN_RETRY || val_long == REDIS_SCAN_PREFIX) {
redis_sock->scan |= val_long;
} else {
break;
}
RETURN_TRUE;
case REDIS_OPT_FAILOVER:
if (c == NULL) RETURN_FALSE;
val_long = zval_get_long(val);
if (val_long == REDIS_FAILOVER_NONE ||
val_long == REDIS_FAILOVER_ERROR ||
val_long == REDIS_FAILOVER_DISTRIBUTE ||
val_long == REDIS_FAILOVER_DISTRIBUTE_SLAVES)
{
c->failover = val_long;
RETURN_TRUE;
}
break;
case REDIS_OPT_MAX_RETRIES:
val_long = zval_get_long(val);
if(val_long >= 0) {
redis_sock->max_retries = val_long;
RETURN_TRUE;
}
break;
case REDIS_OPT_BACKOFF_ALGORITHM:
val_long = zval_get_long(val);
if(val_long >= 0 &&
val_long < REDIS_BACKOFF_ALGORITHMS) {
redis_sock->backoff.algorithm = val_long;
RETURN_TRUE;
}
break;
case REDIS_OPT_BACKOFF_BASE:
val_long = zval_get_long(val);
if(val_long >= 0) {
redis_sock->backoff.base = val_long * 1000;
RETURN_TRUE;
}
break;
case REDIS_OPT_BACKOFF_CAP:
val_long = zval_get_long(val);
if(val_long >= 0) {
redis_sock->backoff.cap = val_long * 1000;
RETURN_TRUE;
}
break;
EMPTY_SWITCH_DEFAULT_CASE()
}
RETURN_FALSE;
}
void redis_prefix_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) {
char *key;
size_t key_len;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &key, &key_len)
==FAILURE)
{
RETURN_FALSE;
}
if (redis_sock->prefix) {
int keyfree = redis_key_prefix(redis_sock, &key, &key_len);
RETVAL_STRINGL(key, key_len);
if (keyfree) efree(key);
} else {
RETURN_STRINGL(key, key_len);
}
}
void redis_serialize_handler(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock)
{
zval *z_val;
char *val;
size_t val_len;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &z_val) == FAILURE) {
RETURN_FALSE;
}
int val_free = redis_serialize(redis_sock, z_val, &val, &val_len);
RETVAL_STRINGL(val, val_len);
if (val_free) efree(val);
}
void redis_unserialize_handler(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock, zend_class_entry *ex)
{
char *value;
size_t value_len;
// Parse our arguments
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &value, &value_len)
== FAILURE)
{
RETURN_FALSE;
}
// We only need to attempt unserialization if we have a serializer running
if (redis_sock->serializer == REDIS_SERIALIZER_NONE) {
// Just return the value that was passed to us
RETURN_STRINGL(value, value_len);
}
zval z_ret;
if (!redis_unserialize(redis_sock, value, value_len, &z_ret)) {
// Badly formed input, throw an exception
zend_throw_exception(ex, "Invalid serialized data, or unserialization error", 0);
RETURN_FALSE;
}
RETURN_ZVAL(&z_ret, 0, 0);
}
void redis_compress_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) {
zend_string *zstr;
size_t len;
char *buf;
int cmp_free;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &zstr) == FAILURE) {
RETURN_FALSE;
}
cmp_free = redis_compress(redis_sock, &buf, &len, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
RETVAL_STRINGL(buf, len);
if (cmp_free) efree(buf);
}
void redis_uncompress_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
zend_class_entry *ex)
{
zend_string *zstr;
size_t len;
char *buf;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &zstr) == FAILURE) {
RETURN_FALSE;
} else if (ZSTR_LEN(zstr) == 0 || redis_sock->compression == REDIS_COMPRESSION_NONE) {
RETURN_STR_COPY(zstr);
}
if (!redis_uncompress(redis_sock, &buf, &len, ZSTR_VAL(zstr), ZSTR_LEN(zstr))) {
zend_throw_exception(ex, "Invalid compressed data or uncompression error", 0);
RETURN_FALSE;
}
RETVAL_STRINGL(buf, len);
efree(buf);
}
void redis_pack_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) {
int valfree;
size_t len;
char *val;
zval *zv;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &zv) == FAILURE) {
RETURN_FALSE;
}
valfree = redis_pack(redis_sock, zv, &val, &len);
RETVAL_STRINGL(val, len);
if (valfree) efree(val);
}
void redis_unpack_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) {
zend_string *str;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) {
RETURN_FALSE;
}
if (redis_unpack(redis_sock, ZSTR_VAL(str), ZSTR_LEN(str), return_value) == 0) {
RETURN_STR_COPY(str);
}
}
/* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */
1
https://gitee.com/mirrorgit/phpredis.git
git@gitee.com:mirrorgit/phpredis.git
mirrorgit
phpredis
phpredis
develop

搜索帮助