-
Notifications
You must be signed in to change notification settings - Fork 0
/
RangeType.php
111 lines (96 loc) · 3.78 KB
/
RangeType.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
<?php
declare(strict_types=1);
namespace Ivory\Type\Postgresql;
use Ivory\Lang\Sql\Types;
use Ivory\Type\ITotallyOrderedType;
use Ivory\Type\TypeBase;
use Ivory\Value\Range;
/**
* Type object for ranges.
*
* @see https://www.postgresql.org/docs/11/rangetypes.html
*/
class RangeType extends TypeBase implements ITotallyOrderedType
{
private $subtype;
public function __construct(string $schemaName, string $name, ITotallyOrderedType $subtype)
{
parent::__construct($schemaName, $name);
$this->subtype = $subtype;
}
public function getSubtype(): ITotallyOrderedType
{
return $this->subtype;
}
public function parseValue(string $extRepr)
{
if (preg_match('~^\s*empty\s*$~i', $extRepr)) {
return Range::empty();
}
$regex = /** @lang PhpRegExp */
'~
^\s*
(?P<open> [[(] )
(?P<lower> "(?:[^"\\\\]|""|\\\\.)*" # either a double-quoted string (backslashes used for escaping,
# or double quotes double for a single double-quote character),
| # or an unquoted string of characters which do not confuse the
(?:[^"\\\\()[\],]|\\\\.)* # parser or are backslash-escaped
)
,
(?P<upper> (?P>lower) ) # the upper-bound follows the same rules as the lower-bound
(?P<close> [])] )
\s*$
~x';
if (!preg_match($regex, $extRepr, $m)) {
throw new \InvalidArgumentException("Invalid value for range {$this->getSchemaName()}.{$this->getName()}");
}
$lowerInc = ($m['open'] == '[');
$upperInc = ($m['close'] == ']');
$lower = $this->parseBoundStr($m['lower']);
$upper = $this->parseBoundStr($m['upper']);
return $this->createParsedRange($lower, $upper, $lowerInc, $upperInc);
}
protected function createParsedRange($lower, $upper, bool $lowerInc, bool $upperInc): Range
{
return Range::fromBounds($lower, $upper, $lowerInc, $upperInc);
}
private function parseBoundStr(string $str)
{
if (strlen($str) == 0) {
return null;
}
if ($str[0] == '"') {
$str = substr($str, 1, -1);
}
$unescaped = preg_replace(['~\\\\(.)~', '~""~'], ['$1', '"'], $str);
return $this->subtype->parseValue($unescaped);
}
public function serializeValue($val, bool $strictType = true): string
{
if ($val === null) {
return $this->typeCastExpr($strictType, 'NULL');
}
if (!$val instanceof Range) {
if (is_array($val) && isset($val[0], $val[1]) && count($val) == 2) {
$val = Range::fromBounds($val[0], $val[1], true, true);
} else {
$message = "Value '$val' is not valid for type {$this->getSchemaName()}.{$this->getName()}";
throw new \InvalidArgumentException($message);
}
}
if ($val->isEmpty()) {
return $this->indicateType($strictType, "'empty'");
}
$boundsSpec = $val->getBoundsSpec();
return sprintf(
"%s.%s(%s,%s%s)",
Types::serializeIdent($this->getSchemaName()),
Types::serializeIdent($this->getName()),
$this->subtype->serializeValue($val->getLower(), false),
$this->subtype->serializeValue($val->getUpper(), false),
($boundsSpec == '[)' || ($boundsSpec == '()' && $val->getLower() === null) ? '' : ",'$boundsSpec'")
);
// FIXME: The fact that '[)' bounds are default is rather conventional, and might not hold for user-defined
// ranges.
}
}