blob: 6bbc32685b27c97b28627c76cca8894fcf0b93d5 [file] [log] [blame]
/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define CFP_LUCY
#define C_LUCY_DOC
#include "XSBind.h"
#include "Lucy/Document/Doc.h"
#include "Lucy/Store/InStream.h"
#include "Lucy/Store/OutStream.h"
#include "Lucy/Util/Json.h"
#include "Clownfish/Util/Memory.h"
lucy_Doc*
lucy_Doc_init(lucy_Doc *self, void *fields, int32_t doc_id) {
dTHX;
lucy_DocIVARS *const ivars = lucy_Doc_IVARS(self);
// Assign.
if (fields) {
if (SvTYPE((SV*)fields) != SVt_PVHV) { THROW(CFISH_ERR, "Not a hash"); }
ivars->fields = SvREFCNT_inc((SV*)fields);
}
else {
ivars->fields = newHV();
}
ivars->doc_id = doc_id;
return self;
}
void
LUCY_Doc_Set_Fields_IMP(lucy_Doc *self, void *fields) {
dTHX;
lucy_DocIVARS *const ivars = lucy_Doc_IVARS(self);
if (ivars->fields) { SvREFCNT_dec((SV*)ivars->fields); }
ivars->fields = SvREFCNT_inc((SV*)fields);
}
uint32_t
LUCY_Doc_Get_Size_IMP(lucy_Doc *self) {
dTHX;
lucy_DocIVARS *const ivars = lucy_Doc_IVARS(self);
return ivars->fields ? HvKEYS((HV*)ivars->fields) : 0;
}
void
LUCY_Doc_Store_IMP(lucy_Doc *self, cfish_String *field, cfish_Obj *value) {
dTHX;
lucy_DocIVARS *const ivars = lucy_Doc_IVARS(self);
const char *key = CFISH_Str_Get_Ptr8(field);
size_t key_size = CFISH_Str_Get_Size(field);
SV *key_sv = newSVpvn(key, key_size);
SV *val_sv = XSBind_cfish_to_perl(aTHX_ value);
SvUTF8_on(key_sv);
(void)hv_store_ent((HV*)ivars->fields, key_sv, val_sv, 0);
// TODO: make this a thread-local instead of creating it every time?
SvREFCNT_dec(key_sv);
}
static SV*
S_nfreeze_fields(pTHX_ lucy_Doc *self) {
lucy_DocIVARS *const ivars = lucy_Doc_IVARS(self);
dSP;
ENTER;
SAVETMPS;
EXTEND(SP, 1);
PUSHMARK(SP);
mPUSHs((SV*)newRV_inc((SV*)ivars->fields));
PUTBACK;
call_pv("Storable::nfreeze", G_SCALAR);
SPAGAIN;
SV *frozen = POPs;
(void)SvREFCNT_inc(frozen);
PUTBACK;
FREETMPS;
LEAVE;
return frozen;
}
void
LUCY_Doc_Serialize_IMP(lucy_Doc *self, lucy_OutStream *outstream) {
dTHX;
lucy_DocIVARS *const ivars = lucy_Doc_IVARS(self);
LUCY_OutStream_Write_CU32(outstream, ivars->doc_id);
SV *frozen = S_nfreeze_fields(aTHX_ self);
STRLEN len;
char *buf = SvPV(frozen, len);
LUCY_OutStream_Write_CU64(outstream, len);
LUCY_OutStream_Write_Bytes(outstream, buf, len);
SvREFCNT_dec(frozen);
}
static HV*
S_thaw_fields(pTHX_ lucy_InStream *instream) {
// Read frozen data into an SV buffer.
size_t len = (size_t)LUCY_InStream_Read_CU64(instream);
SV *buf_sv = newSV(len + 1);
SvPOK_on(buf_sv);
SvCUR_set(buf_sv, len);
char *buf = SvPVX(buf_sv);
LUCY_InStream_Read_Bytes(instream, buf, len);
// Call back to Storable to thaw the frozen hash.
dSP;
ENTER;
SAVETMPS;
EXTEND(SP, 1);
PUSHMARK(SP);
mPUSHs(buf_sv);
PUTBACK;
call_pv("Storable::thaw", G_SCALAR);
SPAGAIN;
SV *frozen = POPs;
if (frozen && !SvROK(frozen)) {
CFISH_THROW(CFISH_ERR, "thaw failed");
}
HV *fields = (HV*)SvRV(frozen);
(void)SvREFCNT_inc((SV*)fields);
PUTBACK;
FREETMPS;
LEAVE;
return fields;
}
lucy_Doc*
LUCY_Doc_Deserialize_IMP(lucy_Doc *self, lucy_InStream *instream) {
dTHX;
int32_t doc_id = (int32_t)LUCY_InStream_Read_CU32(instream);
HV *fields = S_thaw_fields(aTHX_ instream);
lucy_Doc_init(self, fields, doc_id);
SvREFCNT_dec(fields);
return self;
}
cfish_Obj*
LUCY_Doc_Extract_IMP(lucy_Doc *self, cfish_String *field) {
dTHX;
lucy_DocIVARS *const ivars = lucy_Doc_IVARS(self);
cfish_Obj *retval = NULL;
SV **sv_ptr = hv_fetch((HV*)ivars->fields, CFISH_Str_Get_Ptr8(field),
-CFISH_Str_Get_Size(field), 0);
if (sv_ptr) {
retval = XSBind_perl_to_cfish_nullable(aTHX_ *sv_ptr, CFISH_OBJ);
}
return retval;
}
cfish_Vector*
LUCY_Doc_Field_Names_IMP(lucy_Doc *self) {
dTHX;
lucy_DocIVARS *const ivars = lucy_Doc_IVARS(self);
HV *fields = (HV*)ivars->fields;
I32 num_fields = hv_iterinit(fields);
cfish_Vector *retval = cfish_Vec_new(num_fields);
while (num_fields--) {
HE *entry = hv_iternext(fields);
STRLEN key_size;
const char *key = XSBind_hash_key_to_utf8(aTHX_ entry, &key_size);
cfish_String *key_str = cfish_Str_new_from_trusted_utf8(key, key_size);
CFISH_Vec_Push(retval, (cfish_Obj*)key_str);
}
return retval;
}
cfish_Hash*
LUCY_Doc_Dump_IMP(lucy_Doc *self) {
dTHX;
lucy_DocIVARS *const ivars = lucy_Doc_IVARS(self);
cfish_Hash *dump = cfish_Hash_new(0);
CFISH_Hash_Store_Utf8(dump, "_class", 6,
(cfish_Obj*)CFISH_Str_Clone(lucy_Doc_get_class_name(self)));
CFISH_Hash_Store_Utf8(dump, "doc_id", 7,
(cfish_Obj*)cfish_Str_newf("%i32", ivars->doc_id));
SV *fields_sv = newRV_inc((SV*)ivars->fields);
CFISH_Hash_Store_Utf8(dump, "fields", 6,
XSBind_perl_to_cfish(aTHX_ fields_sv, CFISH_HASH));
SvREFCNT_dec(fields_sv);
return dump;
}
lucy_Doc*
LUCY_Doc_Load_IMP(lucy_Doc *self, cfish_Obj *dump) {
dTHX;
cfish_Hash *source = (cfish_Hash*)CFISH_CERTIFY(dump, CFISH_HASH);
cfish_String *class_name = (cfish_String*)CFISH_CERTIFY(
CFISH_Hash_Fetch_Utf8(source, "_class", 6),
CFISH_STRING);
cfish_Class *klass = cfish_Class_singleton(class_name, NULL);
lucy_Doc *loaded = (lucy_Doc*)CFISH_Class_Make_Obj(klass);
cfish_Obj *doc_id = CFISH_CERTIFY(
CFISH_Hash_Fetch_Utf8(source, "doc_id", 7),
CFISH_OBJ);
cfish_Hash *fields = (cfish_Hash*)CFISH_CERTIFY(
CFISH_Hash_Fetch_Utf8(source, "fields", 6),
CFISH_HASH);
SV *fields_sv = XSBind_cfish_to_perl(aTHX_ (cfish_Obj*)fields);
CFISH_UNUSED_VAR(self);
lucy_DocIVARS *const loaded_ivars = lucy_Doc_IVARS(loaded);
loaded_ivars->doc_id = (int32_t)lucy_Json_obj_to_i64(doc_id);
loaded_ivars->fields = SvREFCNT_inc(SvRV(fields_sv));
SvREFCNT_dec(fields_sv);
return loaded;
}
bool
LUCY_Doc_Equals_IMP(lucy_Doc *self, cfish_Obj *other) {
if ((lucy_Doc*)other == self) { return true; }
if (!cfish_Obj_is_a(other, LUCY_DOC)) { return false; }
lucy_DocIVARS *const ivars = lucy_Doc_IVARS(self);
lucy_DocIVARS *const ovars = lucy_Doc_IVARS((lucy_Doc*)other);
if (!!ivars->doc_id ^ !!ovars->doc_id) { return false; }
if (!!ivars->fields ^ !!ovars->fields) { return false; }
// Verify fields. Don't allow any deep data structures.
dTHX;
HV *my_fields = (HV*)ivars->fields;
HV *other_fields = (HV*)ovars->fields;
if (HvKEYS(my_fields) != HvKEYS(other_fields)) { return false; }
I32 num_fields = hv_iterinit(my_fields);
while (num_fields--) {
HE *my_entry = hv_iternext(my_fields);
SV *my_val_sv = HeVAL(my_entry);
STRLEN key_len;
char *key;
if (HeKLEN(my_entry) == HEf_SVKEY) {
SV *key_sv = HeKEY_sv(my_entry);
key = SvPV(key_sv, key_len);
if (SvUTF8(key_sv)) { key_len = -key_len; }
}
else {
key_len = HeKLEN(my_entry);
key = key_len ? HeKEY(my_entry) : Nullch;
if (HeKUTF8(my_entry)) { key_len = -key_len; }
}
SV **const other_val = hv_fetch(other_fields, key, key_len, 0);
if (!other_val) { return false; }
if (!sv_eq(my_val_sv, *other_val)) { return false; }
}
return true;
}
void
LUCY_Doc_Destroy_IMP(lucy_Doc *self) {
lucy_DocIVARS *const ivars = lucy_Doc_IVARS(self);
if (ivars->fields) {
dTHX;
SvREFCNT_dec((SV*)ivars->fields);
}
CFISH_SUPER_DESTROY(self, LUCY_DOC);
}