#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;
}