Skip to content

Commit 42f73bc

Browse files
Merge pull request #171 from stefanak-michal/vector_struct_update
Vector struct update
2 parents 3254553 + f326b1c commit 42f73bc

File tree

3 files changed

+90
-51
lines changed

3 files changed

+90
-51
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Bolt\protocol\v6\structures;
4+
5+
/**
6+
* Type markers for vector data
7+
* @author Michal Stefanak
8+
* @link https://github.com/neo4j-php/Bolt
9+
* @link https://www.neo4j.com/docs/bolt/current/bolt/structure-semantics/#structure-vector
10+
* @package Bolt\protocol\v6\structures
11+
*/
12+
enum TypeMarker: int
13+
{
14+
case INT_8 = 0xC8;
15+
case INT_16 = 0xC9;
16+
case INT_32 = 0xCA;
17+
case INT_64 = 0xCB;
18+
case FLOAT_32 = 0xC6;
19+
case FLOAT_64 = 0xC1;
20+
}

src/protocol/v6/structures/Vector.php

Lines changed: 64 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Bolt\packstream\Bytes;
66
use Bolt\protocol\IStructure;
7+
use Bolt\protocol\v6\structures\TypeMarker;
78

89
/**
910
* Class Vector
@@ -19,116 +20,133 @@ class Vector implements IStructure
1920
public function __construct(
2021
public readonly Bytes $type_marker,
2122
public readonly Bytes $data
22-
) {
23-
}
23+
) {}
2424

2525
public function __toString(): string
2626
{
2727
return json_encode([(string)$this->type_marker, (string)$this->data]);
2828
}
2929

30-
private static array $formats = ['s', 'l', 'q'];
30+
private static array $endiannessFormats = ['s', 'l', 'q'];
3131

3232
/**
3333
* Encode array as vector structure
34+
* This is a helper method to create Vector structure from array of numbers
3435
* @param int[]|float[] $data
36+
* @param TypeMarker|null $type Optional type to force specific data type .. null = auto decide
3537
* @return self
3638
* @throws \InvalidArgumentException
3739
*/
38-
public static function encode(array $data): self
40+
public static function encode(array $data, ?TypeMarker $type = null): self
3941
{
40-
if (count($data) === 0) {
41-
throw new \InvalidArgumentException('Vector cannot be empty');
42+
$anyFloat = false;
43+
foreach ($data as $entry) {
44+
if (!is_int($entry) && !is_float($entry)) {
45+
throw new \InvalidArgumentException('Vector can only contain numeric values');
46+
}
47+
if (!$anyFloat && is_float($entry)) {
48+
$anyFloat = true;
49+
}
4250
}
43-
if (count($data) > 4096) {
44-
throw new \InvalidArgumentException('Vector cannot have more than 4096 elements');
51+
52+
if ($type === null) {
53+
$type = self::detectTypeMarker($anyFloat, count($data) ? min($data) : 0, count($data) ? max($data) : 0);
4554
}
4655

47-
$anyFloat = in_array(true, array_map('is_float', $data));
48-
$minValue = min($data);
49-
$maxValue = max($data);
50-
$marker = 0;
5156
$packFormat = '';
52-
53-
if ($anyFloat) {
54-
if ($minValue >= 1.4e-45 && $maxValue <= 3.4028235e+38) { // Single precision float (FLOAT_32)
55-
$marker = 0xC6;
57+
switch ($type) {
58+
case TypeMarker::FLOAT_32:
5659
$packFormat = 'G';
57-
} else { // Double precision float (FLOAT_64)
58-
$marker = 0xC1;
60+
break;
61+
case TypeMarker::FLOAT_64:
5962
$packFormat = 'E';
60-
}
61-
} else {
62-
if ($minValue >= -128 && $maxValue <= 127) { // INT_8
63-
$marker = 0xC8;
63+
break;
64+
case TypeMarker::INT_8:
6465
$packFormat = 'c';
65-
} elseif ($minValue >= -32768 && $maxValue <= 32767) { // INT_16
66-
$marker = 0xC9;
66+
break;
67+
case TypeMarker::INT_16:
6768
$packFormat = 's';
68-
} elseif ($minValue >= -2147483648 && $maxValue <= 2147483647) { // INT_32
69-
$marker = 0xCA;
69+
break;
70+
case TypeMarker::INT_32:
7071
$packFormat = 'l';
71-
} else { // INT_64
72-
$marker = 0xCB;
72+
break;
73+
case TypeMarker::INT_64:
7374
$packFormat = 'q';
74-
}
75-
}
76-
77-
if ($marker === 0) {
78-
throw new \InvalidArgumentException('Unsupported data type for vector');
75+
break;
7976
}
8077

8178
// Pack the data
8279
$packed = [];
8380
$littleEndian = unpack('S', "\x01\x00")[1] === 1;
8481
foreach ($data as $entry) {
85-
$value = pack($packFormat, $entry);
86-
$packed[] = in_array($packFormat, self::$formats) && $littleEndian ? strrev($value) : $value;
82+
$value = pack($packFormat, $anyFloat ? (float)$entry : (int)$entry);
83+
$packed[] = in_array($packFormat, self::$endiannessFormats) && $littleEndian ? strrev($value) : $value;
8784
}
8885

89-
return new self(new Bytes([chr($marker)]), new Bytes($packed));
86+
return new self(new Bytes([chr($type->value)]), new Bytes($packed));
87+
}
88+
89+
private static function detectTypeMarker(bool $anyFloat, int|float $minValue, int|float $maxValue): TypeMarker
90+
{
91+
if ($anyFloat) {
92+
if ($minValue >= -3.4028235e+38 && $maxValue <= 3.4028235e+38) { // Single precision float (FLOAT_32)
93+
return TypeMarker::FLOAT_32;
94+
} else { // Double precision float (FLOAT_64)
95+
return TypeMarker::FLOAT_64;
96+
}
97+
} else {
98+
if ($minValue >= -128 && $maxValue <= 127) { // INT_8
99+
return TypeMarker::INT_8;
100+
} elseif ($minValue >= -32768 && $maxValue <= 32767) { // INT_16
101+
return TypeMarker::INT_16;
102+
} elseif ($minValue >= -2147483648 && $maxValue <= 2147483647) { // INT_32
103+
return TypeMarker::INT_32;
104+
} else { // INT_64
105+
return TypeMarker::INT_64;
106+
}
107+
}
90108
}
91109

92110
/**
93-
* Decode vector structure .. returns binary $this->data as array
111+
* Decode vector structure .. returns binary $this->data as array of numbers
94112
* @return int[]|float[]
95113
* @throws \InvalidArgumentException
96114
*/
97115
public function decode(): array
98116
{
99117
switch (ord($this->type_marker[0])) {
100-
case 0xC8: // INT_8
118+
case TypeMarker::INT_8->value: // INT_8
101119
$size = 1;
102120
$unpackFormat = 'c';
103121
break;
104-
case 0xC9: // INT_16
122+
case TypeMarker::INT_16->value: // INT_16
105123
$size = 2;
106124
$unpackFormat = 's';
107125
break;
108-
case 0xCA: // INT_32
126+
case TypeMarker::INT_32->value: // INT_32
109127
$size = 4;
110128
$unpackFormat = 'l';
111129
break;
112-
case 0xCB: // INT_64
130+
case TypeMarker::INT_64->value: // INT_64
113131
$size = 8;
114132
$unpackFormat = 'q';
115133
break;
116-
case 0xC6: // FLOAT_32
134+
case TypeMarker::FLOAT_32->value: // FLOAT_32
117135
$size = 4;
118136
$unpackFormat = 'G';
119137
break;
120-
case 0xC1: // FLOAT_64
138+
case TypeMarker::FLOAT_64->value: // FLOAT_64
121139
$size = 8;
122140
$unpackFormat = 'E';
123141
break;
124142
default:
125143
throw new \InvalidArgumentException('Unknown vector type marker: ' . $this->type_marker[0]);
126144
}
127-
145+
128146
$output = [];
129147
$littleEndian = unpack('S', "\x01\x00")[1] === 1;
130-
foreach(mb_str_split((string)$this->data, $size, '8bit') as $value) {
131-
$output[] = unpack($unpackFormat, in_array($unpackFormat, self::$formats) && $littleEndian ? strrev($value) : $value)[1];
148+
foreach (mb_str_split((string)$this->data, $size, '8bit') as $value) {
149+
$output[] = unpack($unpackFormat, in_array($unpackFormat, self::$endiannessFormats) && $littleEndian ? strrev($value) : $value)[1];
132150
}
133151

134152
return $output;

tests/structures/V6/StructuresTest.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,16 @@ public function testVector(AProtocol $protocol)
4343
//unpack
4444
$res = iterator_to_array(
4545
$protocol
46-
->run('CYPHER 25 RETURN vector([1.05, 0.123, 5], 3, FLOAT),
46+
->run(
47+
'CYPHER 25 RETURN vector([1.05, 0.123, 5], 3, FLOAT),
4748
vector([1.05, 0.123, 5], 3, FLOAT32),
4849
vector([5, 543, 342765], 3, INTEGER),
4950
vector([5, -60, 120], 3, INTEGER8),
5051
vector([5, -20000, 30000], 3, INTEGER16),
5152
vector([5, -2000000000, 2000000000], 3, INTEGER32)',
52-
[], ['mode' => 'r'])
53+
[],
54+
['mode' => 'r']
55+
)
5356
->pull()
5457
->getResponses(),
5558
false
@@ -106,8 +109,6 @@ public function testVector(AProtocol $protocol)
106109
public function testVectorExceptions()
107110
{
108111
$this->expectException(\InvalidArgumentException::class);
109-
Vector::encode([]);
110-
$this->expectException(\InvalidArgumentException::class);
111-
Vector::encode(range(1, 5000));
112+
Vector::encode(['abc', 'def']);
112113
}
113114
}

0 commit comments

Comments
 (0)