master
ブランチ master
LittleBeast / src / Std / Lib / Db / Sqlite / Record.php
9.8 KB、308行 生 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 *
43 * 値の表し方:null、int、float、string(TEXT)、Blob(BLOB)。
44 */
45final class Record {
46 /**
47 * 可変長整数を読む(ビッグエンディアン、1バイト7ビット、9バイト目は8ビット)
48 *
49 * @param string $b データ
50 * @param int $pos 位置(読んだ分だけ進む)
51 * @return int 64ビットの符号付き整数
52 */
53 public static function getVarint(string $b, int &$pos): int {
54 $c = ord($b[$pos++] ?? "\0");
55 if ($c < 0x80) return $c;
56 $v = $c & 0x7F;
57 for ($i = 1; $i < 8; $i++) {
58 $c = ord($b[$pos++] ?? "\0");
59 $v = ($v << 7) | ($c & 0x7F);
60 if ($c < 0x80) return $v;
61 }
62 return ($v << 8) | ord($b[$pos++] ?? "\0");
63 }
64
65 /**
66 * 可変長整数を書く
67 *
68 * @param int $v 値(負の数は64ビットの符号なしとして書く)
69 * @return string
70 */
71 public static function putVarint(int $v): string {
72 if ($v >= 0 && $v < 0x80) return chr($v);
73 if ($v < 0 || $v > 0x00FFFFFFFFFFFFFF) {
74 // 9バイト:最後のバイトは8ビット
75 $out = chr($v & 0xFF);
76 $v = ($v >> 8) & 0x00FFFFFFFFFFFFFF;
77 for ($i = 0; $i < 8; $i++) {
78 $out = chr(($v & 0x7F) | 0x80).$out;
79 $v >>= 7;
80 }
81 return $out;
82 }
83 $out = chr($v & 0x7F);
84 $v >>= 7;
85 while ($v > 0) {
86 $out = chr(($v & 0x7F) | 0x80).$out;
87 $v >>= 7;
88 }
89 return $out;
90 }
91
92 /** 可変長整数のバイト数 */
93 public static function varintLen(int $v): int {
94 if ($v < 0) return 9;
95 for ($n = 1; $n < 9; $n++) {
96 if ($v < (1 << (7 * $n))) return $n;
97 }
98 return 9;
99 }
100
101 /**
102 * 値の配列をレコードにする
103 *
104 * @param array $values
105 * @return string
106 */
107 public static function encode(array $values): string {
108 $head = '';
109 $body = '';
110 foreach ($values as $v) {
111 if ($v === null) {
112 $head .= "\0";
113 } else if (is_int($v)) {
114 if ($v === 0) {
115 $head .= "\x08";
116 } else if ($v === 1) {
117 $head .= "\x09";
118 } else if ($v >= -128 && $v <= 127) {
119 $head .= "\x01";
120 $body .= chr($v & 0xFF);
121 } else if ($v >= -32768 && $v <= 32767) {
122 $head .= "\x02";
123 $body .= pack('n', $v & 0xFFFF);
124 } else if ($v >= -8388608 && $v <= 8388607) {
125 $head .= "\x03";
126 $body .= substr(pack('N', $v & 0xFFFFFFFF), 1);
127 } else if ($v >= -2147483648 && $v <= 2147483647) {
128 $head .= "\x04";
129 $body .= pack('N', $v & 0xFFFFFFFF);
130 } else if ($v >= -140737488355328 && $v <= 140737488355327) {
131 $head .= "\x05";
132 $body .= substr(pack('J', $v), 2);
133 } else {
134 $head .= "\x06";
135 $body .= pack('J', $v);
136 }
137 } else if (is_float($v)) {
138 $head .= "\x07";
139 $body .= pack('E', $v);
140 } else if ($v instanceof Blob) {
141 $head .= self::putVarint(strlen($v->data) * 2 + 12);
142 $body .= $v->data;
143 } else {
144 $v = (string)$v;
145 $head .= self::putVarint(strlen($v) * 2 + 13);
146 $body .= $v;
147 }
148 }
149 // ヘッダーの長さ(自分自身を含む)
150 $n = strlen($head) + 1;
151 if ($n >= 0x80) $n = strlen($head) + self::varintLen(strlen($head) + 2);
152 return self::putVarint($n).$head.$body;
153 }
154
155 /**
156 * レコードを値の配列にする
157 *
158 * @param string $rec
159 * @return array
160 */
161 public static function decode(string $rec): array {
162 $pos = 0;
163 $hlen = self::getVarint($rec, $pos);
164 $types = [];
165 while ($pos < $hlen) $types[] = self::getVarint($rec, $pos);
166 $pos = $hlen;
167 $out = [];
168 foreach ($types as $t) {
169 switch ($t) {
170 case 0:
171 $out[] = null;
172 break;
173 case 1:
174 $v = ord($rec[$pos] ?? "\0");
175 $out[] = $v >= 0x80 ? $v - 0x100 : $v;
176 $pos += 1;
177 break;
178 case 2:
179 $v = unpack('n', $rec, $pos)[1];
180 $out[] = $v >= 0x8000 ? $v - 0x10000 : $v;
181 $pos += 2;
182 break;
183 case 3:
184 $v = unpack('N', "\0".substr($rec, $pos, 3))[1];
185 $out[] = $v >= 0x800000 ? $v - 0x1000000 : $v;
186 $pos += 3;
187 break;
188 case 4:
189 $v = unpack('N', $rec, $pos)[1];
190 $out[] = $v >= 0x80000000 ? $v - 0x100000000 : $v;
191 $pos += 4;
192 break;
193 case 5:
194 $v = unpack('J', "\0\0".substr($rec, $pos, 6))[1];
195 $out[] = $v >= 0x800000000000 ? $v - 0x1000000000000 : $v;
196 $pos += 6;
197 break;
198 case 6:
199 $out[] = unpack('J', $rec, $pos)[1];
200 $pos += 8;
201 break;
202 case 7:
203 $out[] = unpack('E', $rec, $pos)[1];
204 $pos += 8;
205 break;
206 case 8:
207 $out[] = 0;
208 break;
209 case 9:
210 $out[] = 1;
211 break;
212 default:
213 if ($t < 12) throw new \UnexpectedValueException("SQLite:不正なシリアル型:{$t}");
214 $len = ($t - 12) >> 1;
215 $s = (string)substr($rec, $pos, $len);
216 $out[] = ($t & 1) ? $s : new Blob($s);
217 $pos += $len;
218 }
219 }
220 return $out;
221 }
222
223 /**
224 * 値の種類の順番(NULL<数値<TEXT<BLOB)
225 */
226 public static function typeClass(mixed $v): int {
227 if ($v === null) return 0;
228 if (is_int($v) || is_float($v)) return 1;
229 if ($v instanceof Blob) return 3;
230 return 2;
231 }
232
233 /**
234 * 2つの値を比べる(SQLiteの順番)
235 *
236 * @param mixed $a
237 * @param mixed $b
238 * @param string $coll 照合順序:BINARY、NOCASE、RTRIM
239 * @return int -1、0、1
240 */
241 public static function compare(mixed $a, mixed $b, string $coll = 'BINARY'): int {
242 $ta = self::typeClass($a);
243 $tb = self::typeClass($b);
244 if ($ta !== $tb) return $ta <=> $tb;
245 switch ($ta) {
246 case 0:
247 return 0;
248 case 1:
249 if (is_int($a) && is_int($b)) return $a <=> $b;
250 // 大きい整数と浮動小数点数
251 if (is_int($a) && is_float($b) && is_finite($b) && abs($b) < 9.2e18) {
252 $fb = (int)floor($b);
253 return $a === $fb ? ($b > $fb ? -1 : 0) : ($a <=> $fb);
254 }
255 if (is_float($a) && is_int($b) && is_finite($a) && abs($a) < 9.2e18) {
256 $fa = (int)floor($a);
257 return $fa === $b ? ($a > $fa ? 1 : 0) : ($fa <=> $b);
258 }
259 if (is_nan($a) || is_nan($b)) return is_nan($a) <=> is_nan($b);
260 return $a <=> $b;
261 case 2:
262 return self::compareText((string)$a, (string)$b, $coll);
263 default:
264 $c = strcmp($a->data, $b->data);
265 return $c < 0 ? -1 : ($c > 0 ? 1 : 0);
266 }
267 }
268
269 public static function compareText(string $a, string $b, string $coll): int {
270 if ($coll === 'NOCASE') {
271 $a = strtolower($a);
272 $b = strtolower($b);
273 } else if ($coll === 'RTRIM') {
274 $a = rtrim($a, ' ');
275 $b = rtrim($b, ' ');
276 }
277 $c = strcmp($a, $b);
278 return $c < 0 ? -1 : ($c > 0 ? 1 : 0);
279 }
280
281 /**
282 * インデックスのキーを比べる
283 *
284 * @param array $a キー(列の値…、rowid)
285 * @param array $b 比べるキー(短ければ、その分だけ比べる)
286 * @param array $colls 列毎の照合順序
287 * @param array $desc 列毎の降順
288 * @return int
289 */
290 public static function compareKeys(array $a, array $b, array $colls, array $desc): int {
291 $n = min(count($a), count($b));
292 for ($i = 0; $i < $n; $i++) {
293 $x = $a[$i];
294 $y = $b[$i];
295 // よく有る場合を先に
296 if (is_int($x) && is_int($y)) {
297 $c = $x <=> $y;
298 } else if (is_string($x) && is_string($y) && ($colls[$i] ?? 'BINARY') === 'BINARY') {
299 $c = strcmp($x, $y);
300 $c = $c < 0 ? -1 : ($c > 0 ? 1 : 0);
301 } else {
302 $c = self::compare($x, $y, $colls[$i] ?? 'BINARY');
303 }
304 if ($c !== 0) return !empty($desc[$i]) ? -$c : $c;
305 }
306 return 0;
307 }
308}