DatabaseDAOPGSQL.php 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * This class is used to test database is well-constructed.
  5. */
  6. class FreshRSS_DatabaseDAOPGSQL extends FreshRSS_DatabaseDAOSQLite {
  7. //PostgreSQL error codes
  8. public const UNDEFINED_COLUMN = '42703';
  9. public const UNDEFINED_TABLE = '42P01';
  10. #[\Override]
  11. public function tablesAreCorrect(): bool {
  12. $db = FreshRSS_Context::systemConf()->db;
  13. $sql = <<<'SQL'
  14. SELECT tablename FROM pg_catalog.pg_tables where tableowner=:tableowner
  15. SQL;
  16. $res = $this->fetchAssoc($sql, [':tableowner' => $db['user']]);
  17. if ($res == null) {
  18. return false;
  19. }
  20. $tables = [
  21. $this->pdo->prefix() . 'category' => false,
  22. $this->pdo->prefix() . 'feed' => false,
  23. $this->pdo->prefix() . 'entry' => false,
  24. $this->pdo->prefix() . 'entrytmp' => false,
  25. $this->pdo->prefix() . 'tag' => false,
  26. $this->pdo->prefix() . 'entrytag' => false,
  27. ];
  28. foreach ($res as $value) {
  29. $tables[array_pop($value)] = true;
  30. }
  31. return count(array_keys($tables, true, true)) === count($tables);
  32. }
  33. /** @return list<array{name:string,type:string,notnull:bool,default:mixed}> */
  34. #[\Override]
  35. public function getSchema(string $table): array {
  36. $sql = <<<'SQL'
  37. SELECT column_name AS field, data_type AS type, column_default AS default, is_nullable AS null
  38. FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = :table_name
  39. SQL;
  40. $res = $this->fetchAssoc($sql, [':table_name' => $this->pdo->prefix() . $table]);
  41. return $res == null ? [] : $this->listDaoToSchema($res);
  42. }
  43. /**
  44. * @param array<string,string|int|bool|null> $dao
  45. * @return array{'name':string,'type':string,'notnull':bool,'default':mixed}
  46. */
  47. #[\Override]
  48. public function daoToSchema(array $dao): array {
  49. return [
  50. 'name' => is_string($dao['field'] ?? null) ? $dao['field'] : '',
  51. 'type' => is_string($dao['type'] ?? null) ? strtolower($dao['type']) : '',
  52. 'notnull' => empty($dao['null']),
  53. 'default' => is_scalar($dao['default'] ?? null) ? $dao['default'] : null,
  54. ];
  55. }
  56. #[\Override]
  57. protected function selectVersion(): string {
  58. return $this->fetchString('SELECT version()') ?? '';
  59. }
  60. #[\Override]
  61. public function size(bool $all = false): int {
  62. if ($all) {
  63. $db = FreshRSS_Context::systemConf()->db;
  64. $res = $this->fetchColumn('SELECT pg_database_size(:base)', 0, [':base' => $db['base']]);
  65. } else {
  66. $sql = <<<SQL
  67. SELECT
  68. pg_total_relation_size('`{$this->pdo->prefix()}category`') +
  69. pg_total_relation_size('`{$this->pdo->prefix()}feed`') +
  70. pg_total_relation_size('`{$this->pdo->prefix()}entry`') +
  71. pg_total_relation_size('`{$this->pdo->prefix()}entrytmp`') +
  72. pg_total_relation_size('`{$this->pdo->prefix()}tag`') +
  73. pg_total_relation_size('`{$this->pdo->prefix()}entrytag`')
  74. SQL;
  75. $res = $this->fetchColumn($sql, 0);
  76. }
  77. return (int)($res[0] ?? -1);
  78. }
  79. #[\Override]
  80. public function optimize(): bool {
  81. $ok = true;
  82. $tables = ['category', 'feed', 'entry', 'entrytmp', 'tag', 'entrytag'];
  83. foreach ($tables as $table) {
  84. $sql = <<<SQL
  85. VACUUM `_{$table}`
  86. SQL;
  87. if ($this->pdo->exec($sql) === false) {
  88. $ok = false;
  89. $info = $this->pdo->errorInfo();
  90. Minz_Log::warning(__METHOD__ . ' error: ' . $sql . ' : ' . json_encode($info));
  91. }
  92. }
  93. return $ok;
  94. }
  95. #[\Override]
  96. public static function strilike(string $haystack, string $needle, bool $contains = false): bool {
  97. if (function_exists('mb_stripos')) {
  98. return $contains ? (mb_stripos($haystack, $needle, 0, 'UTF-8') !== false) :
  99. (mb_strtolower($haystack, 'UTF-8') === mb_strtolower($needle, 'UTF-8'));
  100. }
  101. if (function_exists('transliterator_transliterate')) {
  102. $haystack_ = transliterator_transliterate('Lower', $haystack);
  103. $needle_ = transliterator_transliterate('Lower', $needle);
  104. if ($haystack_ !== false && $needle_ !== false) {
  105. return $contains ? str_contains($haystack_, $needle_) : ($haystack_ === $needle_);
  106. }
  107. }
  108. return $contains ? (stripos($haystack, $needle) !== false) : (strcasecmp($haystack, $needle) === 0);
  109. }
  110. }