Skip to content
This repository was archived by the owner on Jun 18, 2020. It is now read-only.

Commit c0257ee

Browse files
authored
Merge pull request #46 from sandrokeil/feature/vpack-from-array
Added a Vpack::fromArray() implementation using the zend engine to achieve better performance
2 parents 2258777 + 6a51ebe commit c0257ee

File tree

5 files changed

+274
-33
lines changed

5 files changed

+274
-33
lines changed

src/main.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
#include "response.h"
66
#include "vpack.h"
77
#include "cursor.h"
8+
89
#include "exception.h"
10+
#include "vpackImpl.h"
11+
912

1013
extern "C" {
1114

@@ -36,6 +39,7 @@ extern "C" {
3639

3740
extension.onStartup([]() {
3841
arangodb::fuerte::php::registerCustomExceptions();
42+
arangodb::fuerte::php::registerVpackImpl();
3943
});
4044

4145
return extension;
@@ -112,9 +116,6 @@ extern "C" {
112116
Php::Class<arangodb::fuerte::php::Vpack> vpack("ArangoDb\\Vpack");
113117

114118
vpack.method<&arangodb::fuerte::php::Vpack::__construct>("__construct");
115-
vpack.method<&arangodb::fuerte::php::Vpack::fromArray>("fromArray", {
116-
Php::ByVal("array", Php::Type::Array, true)
117-
});
118119
vpack.method<&arangodb::fuerte::php::Vpack::fromJson>("fromJson", {
119120
Php::ByVal("json", Php::Type::String, true)
120121
});

src/vpack.cpp

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,6 @@
22

33
namespace arangodb { namespace fuerte { namespace php {
44

5-
//temporary: replace it with either a c++ json parser (to save overhead from calling json_encode())
6-
//or a native php array to vpack conversion
7-
Php::Value Vpack::fromArray(Php::Parameters &params)
8-
{
9-
auto json = Php::call("json_encode", params[0]);
10-
Vpack* instance = new Vpack();
11-
12-
vp::Parser parser;
13-
try {
14-
parser.parse(json);
15-
}
16-
catch (std::bad_alloc const& e) {
17-
ARANGODB_THROW(RuntimeException(), "Out of memory in %s on line %d");
18-
return NULL;
19-
}
20-
catch (vp::Exception const& e) {
21-
ARANGODB_THROW(RuntimeException(), e.what());
22-
return NULL;
23-
}
24-
25-
instance->builder = *parser.steal();
26-
return Php::Object("ArangoDb\\Vpack", instance);
27-
}
28-
295
Php::Value Vpack::fromJson(Php::Parameters &params)
306
{
317
Vpack* instance = new Vpack();
@@ -83,4 +59,9 @@ namespace arangodb { namespace fuerte { namespace php {
8359
return this->builder;
8460
}
8561

62+
vp::Builder* Vpack::accessBuilder()
63+
{
64+
return &this->builder;
65+
}
66+
8667
}}}

src/vpack.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,27 @@ namespace arangodb { namespace fuerte { namespace php {
1515
{
1616
private:
1717
vp::Builder builder;
18+
1819
vp::Options dumperOptions;
1920

2021
public:
2122
Vpack() = default;
2223

2324
void __construct();
24-
static Php::Value fromArray(Php::Parameters &params);
25+
2526
static Php::Value fromJson(Php::Parameters &params);
2627
Php::Value toHex();
2728
Php::Value toJson();
2829

2930
vp::Slice getSlice();
3031
const vp::Builder& getBuilder() const;
3132

33+
/**
34+
* Grants direct access to the internal builder instance
35+
* @todo find a better solution for this
36+
*/
37+
vp::Builder* accessBuilder();
38+
3239
virtual ~Vpack() = default;
3340

3441
};

src/vpackImpl.h

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
#pragma once
2+
3+
#include <cmath>
4+
using namespace std;
5+
6+
#include <Zend/zend.h>
7+
#include <Zend/zend_exceptions.h>
8+
#include <Zend/zend_API.h>
9+
#include <Zend/zend_inheritance.h>
10+
#include <php.h>
11+
12+
13+
namespace arangodb { namespace fuerte { namespace php {
14+
15+
static void castArray(arangodb::velocypack::Builder* b, HashTable* myht);
16+
static void castAssocArray(arangodb::velocypack::Builder* b, HashTable* myht);
17+
18+
static void castAssocArray(arangodb::velocypack::Builder* b, HashTable* myht)
19+
{
20+
zend_string* key;
21+
zval* data;
22+
zend_ulong index;
23+
24+
char numberBuffer[20];
25+
char* vpackKey;
26+
27+
ZEND_HASH_FOREACH_KEY_VAL_IND(myht, index, key, data) {
28+
29+
if(key) {
30+
vpackKey = ZSTR_VAL(key);
31+
} else {
32+
snprintf(numberBuffer, sizeof(numberBuffer), "%d", index);
33+
vpackKey = numberBuffer;
34+
}
35+
36+
switch(Z_TYPE_P(data)) {
37+
case IS_LONG:
38+
b->add(vpackKey, arangodb::velocypack::Value(Z_LVAL_P(data)));
39+
break;
40+
case IS_STRING:
41+
b->add(vpackKey, arangodb::velocypack::Value(Z_STRVAL_P(data)));
42+
break;
43+
case IS_DOUBLE:
44+
b->add(vpackKey, arangodb::velocypack::Value(Z_DVAL_P(data)));
45+
break;
46+
case IS_TRUE:
47+
b->add(vpackKey, arangodb::velocypack::Value(true));
48+
break;
49+
case IS_FALSE:
50+
b->add(vpackKey, arangodb::velocypack::Value(false));
51+
break;
52+
case IS_NULL:
53+
b->add(vpackKey, arangodb::velocypack::Value(arangodb::velocypack::ValueType::Null));
54+
break;
55+
case IS_ARRAY:
56+
if(HT_IS_PACKED(Z_ARRVAL_P(data)) && HT_IS_WITHOUT_HOLES(Z_ARRVAL_P(data))) {
57+
b->add(vpackKey, arangodb::velocypack::Value(arangodb::velocypack::ValueType::Array));
58+
castArray(b, Z_ARRVAL_P(data));
59+
b->close();
60+
} else {
61+
b->add(vpackKey, arangodb::velocypack::Value(arangodb::velocypack::ValueType::Object));
62+
castAssocArray(b, Z_ARRVAL_P(data));
63+
b->close();
64+
}
65+
break;
66+
case IS_OBJECT: //for now objects will just result in an empty json object
67+
b->add(vpackKey, arangodb::velocypack::Value(arangodb::velocypack::ValueType::Object));
68+
b->close();
69+
break;
70+
default:
71+
break;
72+
}
73+
74+
} ZEND_HASH_FOREACH_END();
75+
}
76+
77+
78+
static void castArray(arangodb::velocypack::Builder* b, HashTable* myht)
79+
{
80+
zval* data;
81+
82+
ZEND_HASH_FOREACH_VAL(myht, data) {
83+
84+
switch(Z_TYPE_P(data)) {
85+
case IS_LONG:
86+
b->add(arangodb::velocypack::Value(Z_LVAL_P(data)));
87+
break;
88+
case IS_STRING:
89+
b->add(arangodb::velocypack::Value(Z_STRVAL_P(data)));
90+
break;
91+
case IS_DOUBLE:
92+
b->add(arangodb::velocypack::Value(Z_DVAL_P(data)));
93+
break;
94+
case IS_TRUE:
95+
b->add(arangodb::velocypack::Value(true));
96+
break;
97+
case IS_FALSE:
98+
b->add(arangodb::velocypack::Value(false));
99+
break;
100+
case IS_NULL:
101+
b->add(arangodb::velocypack::Value(arangodb::velocypack::ValueType::Null));
102+
break;
103+
case IS_ARRAY:
104+
if(HT_IS_PACKED(Z_ARRVAL_P(data)) && HT_IS_WITHOUT_HOLES(Z_ARRVAL_P(data))) {
105+
b->add(arangodb::velocypack::Value(arangodb::velocypack::ValueType::Array));
106+
castArray(b, Z_ARRVAL_P(data));
107+
b->close();
108+
} else {
109+
b->add(arangodb::velocypack::Value(arangodb::velocypack::ValueType::Object));
110+
castAssocArray(b, Z_ARRVAL_P(data));
111+
b->close();
112+
}
113+
break;
114+
case IS_OBJECT:
115+
b->add(arangodb::velocypack::Value(arangodb::velocypack::ValueType::Object));
116+
b->close();
117+
break;
118+
default:
119+
break;
120+
}
121+
122+
} ZEND_HASH_FOREACH_END();
123+
}
124+
125+
}}}
126+
127+
128+
namespace {
129+
130+
void castArray(arangodb::velocypack::Builder* b, HashTable* myht);
131+
void castAssocArray(arangodb::velocypack::Builder* b, HashTable* myht);
132+
133+
/**
134+
* Simple wrapper class just to gain access to the internal _val property
135+
*/
136+
class _Object : public Php::Object
137+
{
138+
public:
139+
using Php::Object::Object;
140+
141+
zval* internalZval()
142+
{
143+
return this->_val;
144+
}
145+
};
146+
147+
148+
PHP_METHOD(VpackImpl, fromArray)
149+
{
150+
zval *arrayValue;
151+
HashTable *myht;
152+
153+
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &arrayValue) == FAILURE) {
154+
return;
155+
}
156+
157+
myht = Z_ARRVAL_P(arrayValue);
158+
159+
arangodb::fuerte::php::Vpack* vpack = new arangodb::fuerte::php::Vpack();
160+
auto b = vpack->accessBuilder();
161+
162+
if(HT_IS_PACKED(myht) && HT_IS_WITHOUT_HOLES(myht)) {
163+
b->add(arangodb::velocypack::Value(arangodb::velocypack::ValueType::Array));
164+
arangodb::fuerte::php::castArray(b, myht);
165+
b->close();
166+
} else {
167+
b->add(arangodb::velocypack::Value(arangodb::velocypack::ValueType::Object));
168+
arangodb::fuerte::php::castAssocArray(b, myht);
169+
b->close();
170+
}
171+
172+
auto instance = _Object("ArangoDb\\Vpack", vpack);
173+
RETURN_ZVAL(instance.internalZval(), 1, 0);
174+
}
175+
176+
177+
ZEND_BEGIN_ARG_INFO_EX(arginfo_array_to_vpack, 0, 0, 1)
178+
ZEND_ARG_INFO(0, array)
179+
ZEND_END_ARG_INFO()
180+
181+
const zend_function_entry array_to_vpack_functions[] = {
182+
PHP_ME(VpackImpl, fromArray, arginfo_array_to_vpack, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
183+
PHP_FE_END
184+
};
185+
}
186+
187+
namespace arangodb { namespace fuerte { namespace php {
188+
189+
void registerVpackImpl()
190+
{
191+
zend_class_entry ce;
192+
INIT_CLASS_ENTRY(ce, "ArangoDb\\VpackImpl", array_to_vpack_functions);
193+
zend_register_internal_class(&ce);
194+
195+
/**
196+
* Here ArangoDb\Vpack extends ArangoDb\VpackImpl in order to inject the
197+
* fromArray() method which is directly implemented using the zend api into
198+
* the ArangoDb\Vpack class
199+
*/
200+
zend_string *name = zend_string_tolower(zend_string_init(ZEND_STRL("ArangoDb\\VpackImpl"), 1));
201+
zend_class_entry *base_ce = static_cast<zend_class_entry*>(zend_hash_find_ptr(CG(class_table), name));
202+
zend_string_release(name);
203+
204+
name = zend_string_tolower(zend_string_init(ZEND_STRL("ArangoDb\\Vpack"), 1));
205+
zend_class_entry* derived_ce = static_cast<zend_class_entry*>(zend_hash_find_ptr(CG(class_table), name));
206+
zend_string_release(name);
207+
208+
assert(base_ce != nullptr);
209+
assert(derived_ce != nullptr);
210+
assert(!derived_ce->create_object || !base_ce->create_object || base_ce->create_object == derived_ce->create_object);
211+
212+
zend_do_inheritance(derived_ce, base_ce);
213+
}
214+
215+
}}}

tests/VpackTest.php

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,15 @@ public function it_creates_vpack_from_array(): void
1919
{
2020
$arr = ["1" => 100, "2" => 1000000, "c" => "d", "test" => true];
2121

22+
/**
23+
* Note: ->toHex() does not necessarily work here due to different compression options
24+
* therefore ->toJson() is used to evaluate equality
25+
*/
2226
$vpack = Vpack::fromArray($arr);
23-
$vpack1 = $vpack->toHex();
27+
$vpack1 = $vpack->toJson();
2428

2529
$vpack = Vpack::fromJson('{"1": 100, "2": 1000000, "c": "d", "test": true}');
26-
$vpack2 = $vpack->toHex();
30+
$vpack2 = $vpack->toJson();
2731

2832
$this->assertTrue($vpack1 === $vpack2);
2933
}
@@ -36,8 +40,7 @@ public function it_fails_creating_vpack_from_invalid_json(): void
3640
{
3741
$this->expectException(\ArangoDb\RuntimeException::class);
3842

39-
$vpack = new Vpack();
40-
$vpack->fromJson("{a:\"b\"}");
43+
Vpack::fromJson("{a:\"b\"}");
4144
}
4245

4346

@@ -57,13 +60,47 @@ public function it_serializes_vpack_into_json(): void
5760

5861
/**
5962
* @test
63+
*
64+
* @todo find a proper setup for this test
6065
*/
61-
public function it_throws_exception_on_serializing_unsupported_types_to_json(): void
66+
/*public function it_throws_exception_on_serializing_unsupported_types_to_json(): void
6267
{
6368
$this->expectException(\ArangoDb\RuntimeException::class);
6469
$this->expectExceptionMessage('Type has no equivalent in JSON');
6570
6671
$vpack = new Vpack(); //empty vpack
6772
$vpack->toJson();
73+
}*/
74+
75+
76+
/**
77+
* @test
78+
*/
79+
public function it_produces_a_proper_vpack(): void
80+
{
81+
$arr = [
82+
"a" => "111",
83+
"b" => 222,
84+
"c" => true,
85+
"d" => false,
86+
"e" => 3.2,
87+
10,
88+
20,
89+
"arr" => [
90+
"a" => "b",
91+
111
92+
],
93+
[23, 58, 10],
94+
[0 => 10, 1 => 20, 3 => 30],
95+
"null" => null,
96+
"obj" => new \stdClass()
97+
];
98+
99+
$vpackFromArray = Vpack::fromArray($arr);
100+
101+
$this->assertEquals(
102+
"{\"0\":10,\"1\":20,\"2\":[23,58,10],\"3\":{\"0\":10,\"1\":20,\"3\":30},\"a\":\"111\",\"arr\":{\"0\":111,\"a\":\"b\"},\"b\":222,\"c\":true,\"d\":false,\"e\":3.2,\"null\":null,\"obj\":{}}",
103+
$vpackFromArray->toJson()
104+
);
68105
}
69106
}

0 commit comments

Comments
 (0)