/**
* This is an attempt at consolidating argument handling for all functions
* in single, multi, and async mode.
*/
#include "perl-couchbase.h"
static plcb_OPTION *
find_valspec(plcb_OPTION *values, const char *key, size_t nkey)
{
plcb_OPTION *ret;
for (ret = values; ret->key; ret++) {
if (nkey != ret->nkey) {
continue;
}
if (strncasecmp(ret->key, key, nkey) == 0) {
return ret;
}
}
return NULL;
}
static int
convert_valspec(plcb_OPTION *dst, SV *src)
{
switch (dst->type) {
case PLCB_ARG_T_PAD:
return 0;
case PLCB_ARG_T_INT:
case PLCB_ARG_T_BOOL: {
int assigned_val = 0;
if (SvTYPE(src) == SVt_NULL) {
assigned_val = 0;
} else {
assigned_val = SvIV(src);
}
*((int*)(dst->value)) = assigned_val;
break;
}
#define EXPECT_RV(subtype, friendly_name) \
if (SvROK(src) == 0 || SvTYPE(SvRV(src)) != subtype) { \
die("Expected %s for %s", friendly_name, dst->key); \
} \
*(void**)dst->value = src;
case PLCB_ARG_T_SV:
*(SV**)dst->value = src;
break;
case PLCB_ARG_T_HV:
EXPECT_RV(SVt_PVHV, "Hash");
break;
case PLCB_ARG_T_AV:
EXPECT_RV(SVt_PVAV, "Array");
break;
case PLCB_ARG_T_CV:
EXPECT_RV(SVt_PVCV, "CODE");
break;
#undef EXPECT_RV
case PLCB_ARG_T_RV:
if (!SvROK(src)) {
die("Expected reference for %s", dst->key);
}
*(SV**)dst->value = src;
break;
case PLCB_ARG_T_CAS: {
uint64_t *cas_p = NULL;
if (SvTYPE(src) == SVt_NULL) {
break;
}
plcb_cas_from_sv(src, cas_p);
if (cas_p) {
*(uint64_t*)dst->value = *cas_p;
}
break;
}
case PLCB_ARG_T_EXP:
case PLCB_ARG_T_EXPTT: {
UV exp_uv = plcb_exp_from_sv(src);
if (dst->type == PLCB_ARG_T_EXP) {
*((UV*)dst->value) = exp_uv;
} else {
*(time_t*)dst->value = exp_uv;
}
break;
}
case PLCB_ARG_T_I64:
*(int64_t*)dst->value = plcb_sv_to_64(src);
break;
case PLCB_ARG_T_U64:
*(uint64_t*)dst->value = plcb_sv_to_u64(src);
break;
case PLCB_ARG_T_U32:
*(uint32_t*)dst->value = SvUV(src);
break;
case PLCB_ARG_T_STRING:
case PLCB_ARG_T_STRING_NN: {
PLCB_XS_STRING_t *str = dst->value;
str->origsv = src;
str->base = SvPV(src, str->len);
if (str->len == 0 && dst->type == PLCB_ARG_T_STRING_NN) {
die("Value cannot be an empty string for %s", dst->key);
}
break;
}
case PLCB_ARG_T_CSTRING:
case PLCB_ARG_T_CSTRING_NN: {
*(const char **)dst->value = SvPV_nolen(src);
if (dst->type == PLCB_ARG_T_CSTRING_NN) {
if (dst->value == NULL|| *(const char*)dst->value == '\0') {
die("Value passed must not be empty for %s", dst->key);
}
}
break;
}
default:
return -1;
break;
}
return 0;
}
int
plcb_extract_args(SV *sv, plcb_OPTION *values)
{
char *cur_key;
I32 klen;
if (SvROK(sv)) {
sv = SvRV(sv);
}
if (SvTYPE(sv) == SVt_PVHV) {
HV *hv = (HV*)sv;
SV *cur_val;
hv_iterinit(hv);
while ( (cur_val = hv_iternextsv(hv, &cur_key, &klen)) ) {
plcb_OPTION *curdst = find_valspec(values, cur_key, klen);
if (!curdst) {
warn("Unrecognized key '%.*s'", (int)klen, cur_key);
continue;
}
if (convert_valspec(curdst, cur_val) == -1) {
die("Bad value for %.*s'", (int)klen, cur_key);
}
curdst->sv = cur_val;
}
} else {
die("Unrecognized options type. Must be hash");
}
return 0;
}
static void
load_doc_options(PLCB_t *parent, AV *ret, plcb_OPTION *values)
{
plcb_OPTION *cur = values;
for (cur = values; cur->value; cur++) {
SV **tmpsv;
int ix;
if (cur->type == PLCB_ARG_T_PAD) {
continue;
}
if (!strcmp(cur->key, PLCB_ARG_K_CAS)) {
ix = PLCB_RETIDX_CAS;
} else if (!strcmp(cur->key,PLCB_ARG_K_EXPIRY)) {
ix = PLCB_RETIDX_EXP;
} else if (!strcmp(cur->key,PLCB_ARG_K_VALUE)) {
ix = PLCB_RETIDX_VALUE;
} else if (!strcmp(cur->key,PLCB_ARG_K_FMT)) {
ix = PLCB_RETIDX_FMTSPEC;
} else {
continue;
}
tmpsv = av_fetch(ret, ix, 0);
if (!tmpsv) {
continue;
}
if (convert_valspec(cur, *tmpsv) == -1) {
die("Couldn't convert %s", cur->key);
}
cur->sv = *tmpsv;
}
}
int
PLCB_args_get(PLCB_t *object, plcb_SINGLEOP *args, lcb_CMDGET *gcmd)
{
if (args->cmdbase == PLCB_CMD_LOCK) {
UV lockexp;
plcb_OPTION opt_specs[] = {
PLCB_KWARG(PLCB_ARG_K_LOCK, EXP, &lockexp),
{NULL}
};
if (!args->cmdopts) {
die("get_and_lock must have " PLCB_ARG_K_LOCK);
}
plcb_extract_args(args->cmdopts, opt_specs);
if (!lockexp) {
die("get_and_lock must have " PLCB_ARG_K_LOCK);
}
gcmd->lock = 1;
gcmd->exptime = lockexp;
} else if (args->cmdbase == PLCB_CMD_GAT || args->cmdbase == PLCB_CMD_TOUCH) {
UV exp = 0;
plcb_OPTION doc_specs[] = {
PLCB_KWARG(PLCB_ARG_K_EXPIRY, EXP, &exp),
{NULL}
};
load_doc_options(object, args->docav, doc_specs);
((lcb_CMDBASE*) gcmd)->exptime = exp;
}
return 0;
}
int
PLCB_args_remove(PLCB_t *object, plcb_SINGLEOP *args, lcb_CMDREMOVE *rcmd)
{
int ignore_cas = 0;
plcb_OPTION doc_specs[] = {
PLCB_KWARG(PLCB_ARG_K_CAS, CAS, &rcmd->cas),
{ NULL }
};
plcb_OPTION opts_specs[] = {
PLCB_KWARG(PLCB_ARG_K_IGNORECAS, BOOL, &ignore_cas),
{NULL}
};
load_doc_options(object, args->docav, doc_specs);
if (args->cmdopts) {
plcb_extract_args(args->cmdopts, opts_specs);
}
if (ignore_cas) {
rcmd->cas = 0;
}
return 0;
}
int
PLCB_args_arithmetic(PLCB_t *object, plcb_SINGLEOP *args, lcb_CMDCOUNTER *acmd)
{
acmd->delta = 1;
plcb_OPTION argspecs[] = {
PLCB_KWARG(PLCB_ARG_K_ARITH_DELTA, I64, &acmd->delta),
PLCB_KWARG(PLCB_ARG_K_ARITH_INITIAL, U64, &acmd->initial),
PLCB_KWARG(PLCB_ARG_K_EXPIRY, EXP, &acmd->exptime),
{ NULL }
};
if (args->cmdopts) {
plcb_extract_args(args->cmdopts, argspecs);
}
if (argspecs[1].sv && argspecs[1].sv != &PL_sv_undef) {
acmd->create = 1;
}
return 0;
}
int
PLCB_args_unlock(PLCB_t *object, plcb_SINGLEOP *args, lcb_CMDUNLOCK *ucmd)
{
plcb_OPTION argspecs[] = {
PLCB_KWARG(PLCB_ARG_K_CAS, CAS, &ucmd->cas),
{ NULL }
};
load_doc_options(object, args->docav, argspecs);
if (!ucmd->cas) {
die("Unlock command must have CAS");
}
return 0;
}
int
PLCB_args_observe(PLCB_t *object, plcb_SINGLEOP *args, lcb_CMDOBSERVE *cmd)
{
int master_only = 0;
plcb_OPTION argspecs[] = {
PLCB_KWARG(PLCB_ARG_K_MASTERONLY, BOOL, &master_only),
{NULL}
};
if (args->cmdopts) {
plcb_extract_args(args->cmdopts, argspecs);
}
if (master_only) {
cmd->cmdflags |= LCB_CMDOBSERVE_F_MASTER_ONLY;
}
return 0;
}
#define is_append(cmd) (cmd) == PLCB_CMD_APPEND || (cmd) == PLCB_CMD_PREPEND
int
PLCB_args_set(PLCB_t *object, plcb_SINGLEOP *args, lcb_CMDSTORE *scmd, plcb_DOCVAL *vspec)
{
UV exp = 0;
SV *dur_sv = NULL;
int ignore_cas = 0;
int persist_to = 0, replicate_to = 0;
plcb_OPTION doc_specs[] = {
PLCB_KWARG(PLCB_ARG_K_VALUE, SV, &vspec->value),
PLCB_KWARG(PLCB_ARG_K_EXPIRY, EXP, &exp),
PLCB_KWARG(PLCB_ARG_K_CAS, CAS, &scmd->cas),
PLCB_KWARG(PLCB_ARG_K_FMT, U32, &vspec->spec),
{NULL}
};
plcb_OPTION opt_specs[] = {
PLCB_KWARG(PLCB_ARG_K_IGNORECAS, BOOL, &ignore_cas),
PLCB_KWARG(PLCB_ARG_K_FRAGMENT, SV, &vspec->value),
PLCB_KWARG(PLCB_ARG_K_PERSIST, INT, &persist_to),
PLCB_KWARG(PLCB_ARG_K_REPLICATE, INT, &replicate_to),
{ NULL }
};
if (is_append(args->cmdbase)) {
doc_specs[0].type = PLCB_ARG_T_PAD;
vspec->spec = PLCB_CF_UTF8;
} else {
vspec->spec = PLCB_CF_JSON;
opt_specs[1].type = PLCB_ARG_T_PAD;
}
load_doc_options(object, args->docav, doc_specs);
if (args->cmdopts) {
plcb_extract_args(args->cmdopts, opt_specs);
}
scmd->exptime = exp;
if (ignore_cas) {
scmd->cas = 0;
}
dur_sv = *av_fetch(args->docav, PLCB_RETIDX_OPTIONS, 1);
if (SvIOK(dur_sv)) {
SvUVX(dur_sv) = PLCB_MKDURABILITY(persist_to, replicate_to);
} else {
sv_setuv(dur_sv, PLCB_MKDURABILITY(persist_to, replicate_to));
}
if (vspec->value == NULL || SvTYPE(vspec->value) == SVt_NULL) {
die("Must have value!");
}
if (is_append(args->cmdbase)) {
if (vspec->spec != PLCB_CF_UTF8 && vspec->spec != PLCB_CF_RAW) {
die("append and prepend must use 'raw' or 'utf8' formats");
}
}
return 0;
}