master
ブランチ master
LittleBeast / src / Std / Lib / Db / Sqlite / Value.php
8.9 KB、240行 生 Blame 履歴
1<?php
2/*************************************************************
3# 076 License
4
5Copyright (c) テクニカル諏訪子
6
7Permission is hereby granted to any person obtaining a copy of the software
8Little Beast (the "Software") to use, modify, merge, copy, publish, distribute,
9sublicense, and/or sell copies of the Software, subject to the following conditions:
10
11 1. **Origin Attribution**:
12 - You must not misrepresent the origin of the Software; you must not claim
13 you created the original Software.
14 - If the Software is used in a product, you must either:
15 a. Provide clear attribution in the product's documentation, user interface,
16 or other visible areas, **OR**
17 b. Pay the original developers a fee they specify in writing.
18 2. **Usage Restriction**:
19 - The Software, or any derivative works, dependencies, or libraries
20 incorporating it, must not be used for censorship or to suppress freedom of
21 speech, expression, or creativity. Prohibited uses include, but are not
22 limited to:
23 - Censorship of so-called "hate speech", visuals, non-mainstream opinions,
24 ideas, or objective reality.
25 - Tools or systems designed to restrict access to information or
26 artistic works.
27 3. **Notice Preservation**:
28 - This license and the above copyright notice must remain intact in all copies
29 of the source code.
30
31THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
32INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
33PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
34HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
35CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
36OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37 */
38namespace Std\Lib\Db\Sqlite;
39
40/**
41 * SQLiteの値の変換(型の親和性、数値・文字列にする、真偽)
42 */
43final class Value {
44 public const INTEGER = 'INTEGER';
45 public const REAL = 'REAL';
46 public const NUMERIC = 'NUMERIC';
47 public const TEXT = 'TEXT';
48 public const BLOB = 'BLOB';
49
50 /**
51 * 宣言された型の親和性(SQLiteの規則)
52 *
53 * @param string $type 例:VARCHAR(255)、BIGINT
54 * @return string
55 */
56 public static function affinity(string $type): string {
57 $t = strtoupper($type);
58 if (str_contains($t, 'INT')) return self::INTEGER;
59 if (str_contains($t, 'CHAR') || str_contains($t, 'CLOB') || str_contains($t, 'TEXT')) return self::TEXT;
60 if ($t === '' || str_contains($t, 'BLOB')) return self::BLOB;
61 if (str_contains($t, 'REAL') || str_contains($t, 'FLOA') || str_contains($t, 'DOUB')) return self::REAL;
62 return self::NUMERIC;
63 }
64
65 /** CASTの型の親和性(空ならBLOB) */
66 public static function castAffinity(string $type): string {
67 return trim($type) === '' ? self::BLOB : self::affinity($type);
68 }
69
70 /**
71 * 列に入れる時の親和性を適用する
72 */
73 public static function apply(mixed $v, string $aff): mixed {
74 if ($v === null || $v instanceof Blob) return $v;
75 switch ($aff) {
76 case self::TEXT:
77 return is_string($v) ? $v : self::toText($v);
78 case self::INTEGER:
79 case self::NUMERIC:
80 if (is_string($v)) {
81 $n = self::parseNumber($v, true);
82 if ($n === null) return $v;
83 $v = $n;
84 }
85 if (is_float($v) && is_finite($v) && floor($v) === $v && abs($v) < 9.2233720368547e18) return (int)$v;
86 return $v;
87 case self::REAL:
88 if (is_string($v)) {
89 $n = self::parseNumber($v, true);
90 return $n === null ? $v : (float)$n;
91 }
92 return (float)$v;
93 }
94 return $v;
95 }
96
97 /**
98 * 文字列全部が数値なら数値(前後の空白は可)
99 *
100 * @param bool $whole trueなら全部が数値の場合だけ、falseなら先頭の数値(無ければ0)
101 * @return int|float|null
102 */
103 public static function parseNumber(string $s, bool $whole): int|float|null {
104 if (preg_match('/^\s*([+-]?)(\d+)(\.\d*)?([eE][+-]?\d+)?\s*$/', $s, $m)
105 || preg_match('/^\s*([+-]?)()(\.\d+)([eE][+-]?\d+)?\s*$/', $s, $m)) {
106 if (($m[3] ?? '') === '' && ($m[4] ?? '') === '') {
107 $digits = ltrim($m[2], '0');
108 if ($digits === '') return 0;
109 if (strlen($digits) < 19 || (strlen($digits) === 19 && strcmp($digits, $m[1] === '-' ? '9223372036854775808' : '9223372036854775807') <= 0)) {
110 if ($m[1] === '-' && $digits === '9223372036854775808') return PHP_INT_MIN;
111 return (int)($m[1].$digits);
112 }
113 }
114 return (float)trim($s);
115 }
116 if ($whole) {
117 // 16進数・無限
118 if (preg_match('/^\s*[+-]?(inf|infinity)\s*$/i', $s)) return null;
119 return null;
120 }
121 if (preg_match('/^\s*[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?/', $s, $m)) {
122 return self::parseNumber($m[0], true) ?? 0;
123 }
124 return 0;
125 }
126
127 /** 計算用の数値(NULLはnull、文字列は先頭の数値) */
128 public static function toNumber(mixed $v): int|float|null {
129 if ($v === null || is_int($v) || is_float($v)) return $v;
130 if ($v instanceof Blob) $v = $v->data;
131 return self::parseNumber((string)$v, false);
132 }
133
134 /** 整数(CAST AS INTEGER) */
135 public static function toInt(mixed $v): ?int {
136 $n = self::toNumber($v);
137 if ($n === null) return null;
138 if (is_float($n)) {
139 if (is_nan($n)) return 0;
140 if ($n >= 9.2233720368547758e18) return PHP_INT_MAX;
141 if ($n <= -9.2233720368547758e18) return PHP_INT_MIN;
142 return (int)$n;
143 }
144 return $n;
145 }
146
147 /** 文字列にする(数値はSQLiteと同じ形) */
148 public static function toText(mixed $v): ?string {
149 if ($v === null) return null;
150 if (is_int($v)) return (string)$v;
151 if (is_float($v)) return self::realToText($v);
152 if ($v instanceof Blob) return $v->data;
153 return (string)$v;
154 }
155
156 /** REALの文字列(%!.15gと同じ) */
157 public static function realToText(float $f): string {
158 if (is_nan($f)) return 'NaN';
159 if (is_infinite($f)) return $f > 0 ? 'Inf' : '-Inf';
160 if ($f == 0.0) return '0.0';
161 $e = sprintf('%.14e', $f);
162 [$mant, $exp] = explode('e', $e);
163 $exp = (int)$exp;
164 if ($exp < -4 || $exp >= 15) {
165 $mant = rtrim(rtrim($mant, '0'), '.');
166 if (!str_contains($mant, '.')) $mant .= '.0';
167 return $mant.'e'.($exp < 0 ? '-' : '+').str_pad((string)abs($exp), 2, '0', STR_PAD_LEFT);
168 }
169 $s = sprintf('%.'.max(0, 14 - $exp).'F', $f);
170 if (str_contains($s, '.')) $s = rtrim(rtrim($s, '0'), '.');
171 if (!str_contains($s, '.')) $s .= '.0';
172 return $s;
173 }
174
175 /**
176 * 真偽(NULLはnull)
177 */
178 public static function truth(mixed $v): ?bool {
179 if ($v === null) return null;
180 if (is_int($v)) return $v !== 0;
181 if (is_float($v)) return $v != 0.0;
182 $n = self::toNumber($v);
183 return $n != 0;
184 }
185
186 /**
187 * 比べる前の親和性(SQLiteの比較の規則)
188 *
189 * @param array $l [値, 親和性]
190 * @param array $r [値, 親和性]
191 * @return array [左の値, 右の値]
192 */
193 public static function prepareCompare(mixed $a, string $affA, mixed $b, string $affB): array {
194 $numA = $affA === self::INTEGER || $affA === self::REAL || $affA === self::NUMERIC;
195 $numB = $affB === self::INTEGER || $affB === self::REAL || $affB === self::NUMERIC;
196 if ($numA && !$numB) {
197 if (is_string($b)) $b = self::apply($b, self::NUMERIC);
198 } else if ($numB && !$numA) {
199 if (is_string($a)) $a = self::apply($a, self::NUMERIC);
200 } else if ($affA === self::TEXT && $affB === '') {
201 if (is_int($b) || is_float($b)) $b = self::toText($b);
202 } else if ($affB === self::TEXT && $affA === '') {
203 if (is_int($a) || is_float($a)) $a = self::toText($a);
204 }
205 return [$a, $b];
206 }
207
208 /** SQLのリテラル(quote()) */
209 public static function quote(mixed $v): string {
210 if ($v === null) return 'NULL';
211 if (is_int($v)) return (string)$v;
212 if (is_float($v)) return self::realToText($v);
213 if ($v instanceof Blob) return "X'".strtoupper(bin2hex($v->data))."'";
214 return "'".str_replace("'", "''", (string)$v)."'";
215 }
216
217 /** typeof() */
218 public static function type(mixed $v): string {
219 if ($v === null) return 'null';
220 if (is_int($v)) return 'integer';
221 if (is_float($v)) return 'real';
222 if ($v instanceof Blob) return 'blob';
223 return 'text';
224 }
225
226 /** GROUP BY・DISTINCT用のキー */
227 public static function key(mixed $v, string $coll = 'BINARY'): string {
228 if ($v === null) return 'n';
229 if (is_int($v)) return 'i'.$v;
230 if (is_float($v)) {
231 if (is_finite($v) && floor($v) === $v && abs($v) < 9.2e18) return 'i'.(int)$v;
232 return 'f'.$v;
233 }
234 if ($v instanceof Blob) return 'b'.strlen($v->data).':'.$v->data;
235 $s = (string)$v;
236 if ($coll === 'NOCASE') $s = strtolower($s);
237 else if ($coll === 'RTRIM') $s = rtrim($s, ' ');
238 return 's'.strlen($s).':'.$s;
239 }
240}