1<?php 2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project 3// 4// All Rights Reserved. See copyright.txt for details and a complete list of authors. 5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details. 6// $Id$ 7 8class Search_MySql_Table extends TikiDb_Table 9{ 10 private $definition = false; 11 private $indexes = []; 12 private $exists = null; 13 14 private $schemaBuffer; 15 private $dataBuffer; 16 private $tfTranslator; 17 18 function __construct($db, $table) 19 { 20 parent::__construct($db, $table); 21 22 $table = $this->escapeIdentifier($this->tableName); 23 $this->schemaBuffer = new Search_MySql_QueryBuffer($db, 2000, "ALTER TABLE $table "); 24 $this->dataBuffer = new Search_MySql_QueryBuffer($db, 100, '-- '); // Null Object, replaced later 25 $this->tfTranslator = new Search_MySql_TrackerFieldTranslator; 26 } 27 28 function __destruct() 29 { 30 try { 31 $this->flush(); 32 } catch (Search_MySql_Exception $e) { 33 # ignore this to cleanly destruct the object 34 } 35 } 36 37 function drop() 38 { 39 $table = $this->escapeIdentifier($this->tableName); 40 $this->db->query("DROP TABLE IF EXISTS $table"); 41 $this->definition = false; 42 $this->exists = false; 43 44 $this->emptyBuffer(); 45 } 46 47 function exists() 48 { 49 if (is_null($this->exists)) { 50 $tables = $this->db->listTables(); 51 $this->exists = in_array($this->tableName, $tables); 52 } 53 54 return $this->exists; 55 } 56 57 function insert(array $values, $ignore = false) 58 { 59 $keySet = implode(', ', array_map([$this, 'escapeIdentifier'], array_map([$this->tfTranslator, 'shortenize'], array_keys($values)))); 60 61 $valueSet = '(' . implode(', ', array_map([$this->db, 'qstr'], $values)) . ')'; 62 63 $this->addToBuffer($keySet, $valueSet); 64 65 return 0; 66 } 67 68 function ensureHasField($fieldName, $type) 69 { 70 $this->loadDefinition(); 71 72 if (! isset($this->definition[$fieldName])) { 73 $this->addField($fieldName, $type); 74 $this->definition[$fieldName] = $type; 75 } 76 } 77 78 function hasIndex($fieldName, $type) 79 { 80 $this->loadDefinition(); 81 82 $indexName = $fieldName . '_' . $type; 83 return isset($this->indexes[$indexName]); 84 } 85 86 function ensureHasIndex($fieldName, $type) 87 { 88 global $prefs; 89 90 $this->loadDefinition(); 91 92 if (! isset($this->definition[$fieldName]) && $prefs['search_error_missing_field'] === 'y') { 93 if (preg_match('/^tracker_field_/', $fieldName)) { 94 $msg = tr('Field %0 does not exist in the current index. Please check field permanent name and if you have any items in that tracker.', $fieldName); 95 if ($prefs['unified_exclude_nonsearchable_fields'] === 'y') { 96 $msg .= ' '.tr('You have disabled indexing non-searchable tracker fields. Check if this field is marked as searchable.'); 97 } 98 } else { 99 $msg = tr('Field %0 does not exist in the current index. If this is a tracker field, the proper syntax is tracker_field_%0.', $fieldName, $fieldName); 100 } 101 $e = new Search_MySql_QueryException($msg); 102 if ($fieldName == 'tracker_id') { 103 $e->suppress_feedback = true; 104 } 105 throw $e; 106 } 107 108 $indexName = $fieldName . '_' . $type; 109 110 // Static MySQL limit on 64 indexes per table 111 if (! isset($this->indexes[$indexName]) && count($this->indexes) < 64) { 112 if ($type == 'fulltext') { 113 $this->addFullText($fieldName); 114 } elseif ($type == 'index') { 115 $this->addIndex($fieldName); 116 } 117 118 $this->indexes[$indexName] = true; 119 } 120 } 121 122 private function loadDefinition() 123 { 124 if (! empty($this->definition)) { 125 return; 126 } 127 128 if (! $this->exists()) { 129 $this->createTable(); 130 $this->loadDefinition(); 131 } 132 133 $table = $this->escapeIdentifier($this->tableName); 134 $result = $this->db->fetchAll("DESC $table"); 135 $this->definition = []; 136 foreach ($result as $row) { 137 $this->definition[$this->tfTranslator->normalize($row['Field'])] = $row['Type']; 138 } 139 140 $result = $this->db->fetchAll("SHOW INDEXES FROM $table"); 141 $this->indexes = []; 142 foreach ($result as $row) { 143 $this->indexes[$this->tfTranslator->normalize($row['Key_name'])] = true; 144 } 145 } 146 147 private function createTable() 148 { 149 $table = $this->escapeIdentifier($this->tableName); 150 $this->db->query( 151 "CREATE TABLE IF NOT EXISTS $table ( 152 `id` INT NOT NULL AUTO_INCREMENT, 153 `object_type` VARCHAR(15) NOT NULL, 154 `object_id` VARCHAR(235) NOT NULL, 155 PRIMARY KEY(`id`), 156 INDEX (`object_type`, `object_id`(160)) 157 ) ENGINE=MyISAM" 158 ); 159 $this->exists = true; 160 161 $this->emptyBuffer(); 162 } 163 164 private function addField($fieldName, $type) 165 { 166 $table = $this->escapeIdentifier($this->tableName); 167 $fieldName = $this->escapeIdentifier($this->tfTranslator->shortenize($fieldName)); 168 $this->schemaBuffer->push("ADD COLUMN $fieldName $type"); 169 } 170 171 private function addIndex($fieldName) 172 { 173 $currentType = $this->definition[$fieldName]; 174 $alterType = null; 175 176 $indexName = $fieldName . '_index'; 177 $table = $this->escapeIdentifier($this->tableName); 178 $escapedIndex = $this->escapeIdentifier($this->tfTranslator->shortenize($indexName)); 179 $escapedField = $this->escapeIdentifier($this->tfTranslator->shortenize($fieldName)); 180 181 if ($currentType == 'TEXT' || $currentType == 'text') { 182 $this->schemaBuffer->push("MODIFY COLUMN $escapedField VARCHAR(235)"); 183 $this->definition[$fieldName] = 'VARCHAR(235)'; 184 } 185 186 $this->schemaBuffer->push("ADD INDEX $escapedIndex ($escapedField)"); 187 } 188 189 private function addFullText($fieldName) 190 { 191 $indexName = $fieldName . '_fulltext'; 192 $table = $this->escapeIdentifier($this->tableName); 193 $escapedIndex = $this->escapeIdentifier($this->tfTranslator->shortenize($indexName)); 194 $escapedField = $this->escapeIdentifier($this->tfTranslator->shortenize($fieldName)); 195 $this->schemaBuffer->push("ADD FULLTEXT INDEX $escapedIndex ($escapedField)"); 196 } 197 198 private function emptyBuffer() 199 { 200 $this->schemaBuffer->clear(); 201 $this->dataBuffer->clear(); 202 } 203 204 private function addToBuffer($keySet, $valueSet) 205 { 206 $this->schemaBuffer->flush(); 207 208 $this->dataBuffer->setPrefix("INSERT INTO {$this->escapeIdentifier($this->tableName)} ($keySet) VALUES "); 209 $this->dataBuffer->push($valueSet); 210 } 211 212 function flush() 213 { 214 $this->schemaBuffer->flush(); 215 $this->dataBuffer->flush(); 216 } 217} 218