= 0 && $v < 0x80) return chr($v); if ($v < 0 || $v > 0x00FFFFFFFFFFFFFF) { // 9バイト:最後のバイトは8ビット $out = chr($v & 0xFF); $v = ($v >> 8) & 0x00FFFFFFFFFFFFFF; for ($i = 0; $i < 8; $i++) { $out = chr(($v & 0x7F) | 0x80).$out; $v >>= 7; } return $out; } $out = chr($v & 0x7F); $v >>= 7; while ($v > 0) { $out = chr(($v & 0x7F) | 0x80).$out; $v >>= 7; } return $out; } /** 可変長整数のバイト数 */ public static function varintLen(int $v): int { if ($v < 0) return 9; for ($n = 1; $n < 9; $n++) { if ($v < (1 << (7 * $n))) return $n; } return 9; } /** * 値の配列をレコードにする * * @param array $values * @return string */ public static function encode(array $values): string { $head = ''; $body = ''; foreach ($values as $v) { if ($v === null) { $head .= "\0"; } else if (is_int($v)) { if ($v === 0) { $head .= "\x08"; } else if ($v === 1) { $head .= "\x09"; } else if ($v >= -128 && $v <= 127) { $head .= "\x01"; $body .= chr($v & 0xFF); } else if ($v >= -32768 && $v <= 32767) { $head .= "\x02"; $body .= pack('n', $v & 0xFFFF); } else if ($v >= -8388608 && $v <= 8388607) { $head .= "\x03"; $body .= substr(pack('N', $v & 0xFFFFFFFF), 1); } else if ($v >= -2147483648 && $v <= 2147483647) { $head .= "\x04"; $body .= pack('N', $v & 0xFFFFFFFF); } else if ($v >= -140737488355328 && $v <= 140737488355327) { $head .= "\x05"; $body .= substr(pack('J', $v), 2); } else { $head .= "\x06"; $body .= pack('J', $v); } } else if (is_float($v)) { $head .= "\x07"; $body .= pack('E', $v); } else if ($v instanceof Blob) { $head .= self::putVarint(strlen($v->data) * 2 + 12); $body .= $v->data; } else { $v = (string)$v; $head .= self::putVarint(strlen($v) * 2 + 13); $body .= $v; } } // ヘッダーの長さ(自分自身を含む) $n = strlen($head) + 1; if ($n >= 0x80) $n = strlen($head) + self::varintLen(strlen($head) + 2); return self::putVarint($n).$head.$body; } /** * レコードを値の配列にする * * @param string $rec * @return array */ public static function decode(string $rec): array { $pos = 0; $hlen = self::getVarint($rec, $pos); $types = []; while ($pos < $hlen) $types[] = self::getVarint($rec, $pos); $pos = $hlen; $out = []; foreach ($types as $t) { switch ($t) { case 0: $out[] = null; break; case 1: $v = ord($rec[$pos] ?? "\0"); $out[] = $v >= 0x80 ? $v - 0x100 : $v; $pos += 1; break; case 2: $v = unpack('n', $rec, $pos)[1]; $out[] = $v >= 0x8000 ? $v - 0x10000 : $v; $pos += 2; break; case 3: $v = unpack('N', "\0".substr($rec, $pos, 3))[1]; $out[] = $v >= 0x800000 ? $v - 0x1000000 : $v; $pos += 3; break; case 4: $v = unpack('N', $rec, $pos)[1]; $out[] = $v >= 0x80000000 ? $v - 0x100000000 : $v; $pos += 4; break; case 5: $v = unpack('J', "\0\0".substr($rec, $pos, 6))[1]; $out[] = $v >= 0x800000000000 ? $v - 0x1000000000000 : $v; $pos += 6; break; case 6: $out[] = unpack('J', $rec, $pos)[1]; $pos += 8; break; case 7: $out[] = unpack('E', $rec, $pos)[1]; $pos += 8; break; case 8: $out[] = 0; break; case 9: $out[] = 1; break; default: if ($t < 12) throw new \UnexpectedValueException("SQLite:不正なシリアル型:{$t}"); $len = ($t - 12) >> 1; $s = (string)substr($rec, $pos, $len); $out[] = ($t & 1) ? $s : new Blob($s); $pos += $len; } } return $out; } /** * 値の種類の順番(NULL<数値<TEXT<BLOB) */ public static function typeClass(mixed $v): int { if ($v === null) return 0; if (is_int($v) || is_float($v)) return 1; if ($v instanceof Blob) return 3; return 2; } /** * 2つの値を比べる(SQLiteの順番) * * @param mixed $a * @param mixed $b * @param string $coll 照合順序:BINARY、NOCASE、RTRIM * @return int -1、0、1 */ public static function compare(mixed $a, mixed $b, string $coll = 'BINARY'): int { $ta = self::typeClass($a); $tb = self::typeClass($b); if ($ta !== $tb) return $ta <=> $tb; switch ($ta) { case 0: return 0; case 1: if (is_int($a) && is_int($b)) return $a <=> $b; // 大きい整数と浮動小数点数 if (is_int($a) && is_float($b) && is_finite($b) && abs($b) < 9.2e18) { $fb = (int)floor($b); return $a === $fb ? ($b > $fb ? -1 : 0) : ($a <=> $fb); } if (is_float($a) && is_int($b) && is_finite($a) && abs($a) < 9.2e18) { $fa = (int)floor($a); return $fa === $b ? ($a > $fa ? 1 : 0) : ($fa <=> $b); } if (is_nan($a) || is_nan($b)) return is_nan($a) <=> is_nan($b); return $a <=> $b; case 2: return self::compareText((string)$a, (string)$b, $coll); default: $c = strcmp($a->data, $b->data); return $c < 0 ? -1 : ($c > 0 ? 1 : 0); } } public static function compareText(string $a, string $b, string $coll): int { if ($coll === 'NOCASE') { $a = strtolower($a); $b = strtolower($b); } else if ($coll === 'RTRIM') { $a = rtrim($a, ' '); $b = rtrim($b, ' '); } $c = strcmp($a, $b); return $c < 0 ? -1 : ($c > 0 ? 1 : 0); } /** * インデックスのキーを比べる * * @param array $a キー(列の値…、rowid) * @param array $b 比べるキー(短ければ、その分だけ比べる) * @param array $colls 列毎の照合順序 * @param array $desc 列毎の降順 * @return int */ public static function compareKeys(array $a, array $b, array $colls, array $desc): int { $n = min(count($a), count($b)); for ($i = 0; $i < $n; $i++) { $x = $a[$i]; $y = $b[$i]; // よく有る場合を先に if (is_int($x) && is_int($y)) { $c = $x <=> $y; } else if (is_string($x) && is_string($y) && ($colls[$i] ?? 'BINARY') === 'BINARY') { $c = strcmp($x, $y); $c = $c < 0 ? -1 : ($c > 0 ? 1 : 0); } else { $c = self::compare($x, $y, $colls[$i] ?? 'BINARY'); } if ($c !== 0) return !empty($desc[$i]) ? -$c : $c; } return 0; } }