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

Commit d20bbe8

Browse files
Added a Vpack::fromArray() implementation using the zend engine to achieve better performance
1 parent ff67a6f commit d20bbe8

File tree

5 files changed

+262
-33
lines changed

5 files changed

+262
-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;
@@ -103,9 +107,6 @@ extern "C" {
103107
Php::Class<arangodb::fuerte::php::Vpack> vpack("ArangoDb\\Vpack");
104108

105109
vpack.method<&arangodb::fuerte::php::Vpack::__construct>("__construct");
106-
vpack.method<&arangodb::fuerte::php::Vpack::fromArray>("fromArray", {
107-
Php::ByVal("array", Php::Type::Array, true)
108-
});
109110
vpack.method<&arangodb::fuerte::php::Vpack::fromJson>("fromJson", {
110111
Php::ByVal("json", Php::Type::String, true)
111112
});

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: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
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_ARRAY:
53+
if(HT_IS_PACKED(Z_ARRVAL_P(data)) && HT_IS_WITHOUT_HOLES(Z_ARRVAL_P(data))) {
54+
b->add(vpackKey, arangodb::velocypack::Value(arangodb::velocypack::ValueType::Array));
55+
castArray(b, Z_ARRVAL_P(data));
56+
b->close();
57+
} else {
58+
b->add(vpackKey, arangodb::velocypack::Value(arangodb::velocypack::ValueType::Object));
59+
castAssocArray(b, Z_ARRVAL_P(data));
60+
b->close();
61+
}
62+
break;
63+
case IS_OBJECT:
64+
break;
65+
default:
66+
break;
67+
}
68+
69+
} ZEND_HASH_FOREACH_END();
70+
}
71+
72+
73+
static void castArray(arangodb::velocypack::Builder* b, HashTable* myht)
74+
{
75+
zval* data;
76+
77+
ZEND_HASH_FOREACH_VAL(myht, data) {
78+
79+
switch(Z_TYPE_P(data)) {
80+
case IS_LONG:
81+
b->add(arangodb::velocypack::Value(Z_LVAL_P(data)));
82+
break;
83+
case IS_STRING:
84+
b->add(arangodb::velocypack::Value(Z_STRVAL_P(data)));
85+
break;
86+
case IS_DOUBLE:
87+
b->add(arangodb::velocypack::Value(Z_DVAL_P(data)));
88+
break;
89+
case IS_TRUE:
90+
b->add(arangodb::velocypack::Value(true));
91+
break;
92+
case IS_FALSE:
93+
b->add(arangodb::velocypack::Value(false));
94+
break;
95+
case IS_ARRAY:
96+
if(HT_IS_PACKED(Z_ARRVAL_P(data)) && HT_IS_WITHOUT_HOLES(Z_ARRVAL_P(data))) {
97+
b->add(arangodb::velocypack::Value(arangodb::velocypack::ValueType::Array));
98+
castArray(b, Z_ARRVAL_P(data));
99+
b->close();
100+
} else {
101+
b->add(arangodb::velocypack::Value(arangodb::velocypack::ValueType::Object));
102+
castAssocArray(b, Z_ARRVAL_P(data));
103+
b->close();
104+
}
105+
break;
106+
case IS_OBJECT:
107+
break;
108+
default:
109+
break;
110+
}
111+
112+
} ZEND_HASH_FOREACH_END();
113+
}
114+
115+
}}}
116+
117+
118+
namespace {
119+
120+
void castArray(arangodb::velocypack::Builder* b, HashTable* myht);
121+
void castAssocArray(arangodb::velocypack::Builder* b, HashTable* myht);
122+
123+
/**
124+
* Simple wrapper class just to gain access to the internal _val property
125+
*/
126+
class _Object : public Php::Object
127+
{
128+
public:
129+
using Php::Object::Object;
130+
131+
zval* internalZval()
132+
{
133+
return this->_val;
134+
}
135+
};
136+
137+
138+
PHP_METHOD(VpackImpl, fromArray)
139+
{
140+
zval *arrayValue;
141+
HashTable *myht;
142+
143+
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &arrayValue) == FAILURE) {
144+
return;
145+
}
146+
147+
myht = Z_ARRVAL_P(arrayValue);
148+
149+
arangodb::fuerte::php::Vpack* vpack = new arangodb::fuerte::php::Vpack();
150+
auto b = vpack->accessBuilder();
151+
152+
if(HT_IS_PACKED(myht) && HT_IS_WITHOUT_HOLES(myht)) {
153+
b->add(arangodb::velocypack::Value(arangodb::velocypack::ValueType::Array));
154+
arangodb::fuerte::php::castArray(b, myht);
155+
b->close();
156+
} else {
157+
b->add(arangodb::velocypack::Value(arangodb::velocypack::ValueType::Object));
158+
arangodb::fuerte::php::castAssocArray(b, myht);
159+
b->close();
160+
}
161+
162+
auto instance = _Object("ArangoDb\\Vpack", vpack);
163+
RETURN_ZVAL(instance.internalZval(), 1, 0);
164+
}
165+
166+
167+
ZEND_BEGIN_ARG_INFO_EX(arginfo_array_to_vpack, 0, 0, 1)
168+
ZEND_ARG_INFO(0, array)
169+
ZEND_END_ARG_INFO()
170+
171+
const zend_function_entry array_to_vpack_functions[] = {
172+
PHP_ME(VpackImpl, fromArray, arginfo_array_to_vpack, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
173+
PHP_FE_END
174+
};
175+
}
176+
177+
namespace arangodb { namespace fuerte { namespace php {
178+
179+
void registerVpackImpl()
180+
{
181+
zend_class_entry ce;
182+
INIT_CLASS_ENTRY(ce, "ArangoDb\\VpackImpl", array_to_vpack_functions);
183+
zend_register_internal_class(&ce);
184+
185+
/**
186+
* Here ArangoDb\Vpack extends ArangoDb\VpackImpl in order to inject the
187+
* fromArray() method which is directly implemented using the zend api into
188+
* the ArangoDb\Vpack class
189+
*/
190+
zend_string *name = zend_string_tolower(zend_string_init(ZEND_STRL("ArangoDb\\VpackImpl"), 1));
191+
zend_class_entry *base_ce = static_cast<zend_class_entry*>(zend_hash_find_ptr(CG(class_table), name));
192+
zend_string_release(name);
193+
194+
name = zend_string_tolower(zend_string_init(ZEND_STRL("ArangoDb\\Vpack"), 1));
195+
zend_class_entry* derived_ce = static_cast<zend_class_entry*>(zend_hash_find_ptr(CG(class_table), name));
196+
zend_string_release(name);
197+
198+
assert(base_ce != nullptr);
199+
assert(derived_ce != nullptr);
200+
assert(!derived_ce->create_object || !base_ce->create_object || base_ce->create_object == derived_ce->create_object);
201+
202+
zend_do_inheritance(derived_ce, base_ce);
203+
}
204+
205+
}}}

tests/VpackTest.php

Lines changed: 40 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,45 @@ 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+
];
96+
97+
$vpackFromArray = Vpack::fromArray($arr);
98+
99+
$this->assertEquals(
100+
"{\"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}",
101+
$vpackFromArray->toJson()
102+
);
68103
}
69104
}

0 commit comments

Comments
 (0)