Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
word.php
1<?php
9
10use Bitrix\Main;
14
18
19Loc::loadMessages(__FILE__);
20
37final class WordTable extends Entity\DataManager implements \Serializable
38{
39 protected $procData = array();
40 protected $word2LocationInserter = null;
41 protected $dictionaryInserter = null;
42
43 protected $dictionaryIndex = array();
44
45 const STEP_SIZE = 10000;
46 const MTU = 9999;
47
48 public function serialize(): ?string
49 {
50 return serialize($this->procData);
51 }
52
53 public function unserialize($data): void
54 {
55 $this->procData = unserialize($data, ['allowed_classes' => false]);
56 $this->initInsertHandles();
57 }
58
59 public function __serialize()
60 {
61 return $this->procData;
62 }
63
64 public function __unserialize($data): void
65 {
66 if (is_array($data))
67 {
68 $this->procData = $data;
69 $this->initInsertHandles();
70 }
71 }
72
73 public static function getFilePath()
74 {
75 return __FILE__;
76 }
77
78 // this for keeping word dictionary
79 public static function getTableName()
80 {
81 return 'b_sale_loc_search_word';
82 }
83
84 // this for keeping links between location-and-word-id
85 public static function getTableNameWord2Location()
86 {
87 return 'b_sale_loc_search_w2l';
88 }
89
90 // this for merge, temporal table
91 public static function getTableNamePositionTemporal()
92 {
93 return 'b_sale_loc_search_wt';
94 }
95
96 public function __construct($parameters)
97 {
98 $this->resetProcess();
99 $this->initInsertHandles();
100
101 if(is_array($parameters['TYPES']) && !empty($parameters['TYPES']))
102 {
103 $this->procData['ALLOWED_TYPES'] = array_unique($parameters['TYPES']);
104 }
105 if(is_array($parameters['LANGS']) && !empty($parameters['LANGS']))
106 {
107 $this->procData['ALLOWED_LANGS'] = array_unique($parameters['LANGS']);
108 }
109
110 $this->procData['CURRENT_LOCATION'] = false;
111 $this->procData['CURRENT_LOCATION_WORDS'] = array();
112 }
113
114 public function initInsertHandles()
115 {
116 $this->word2LocationInserter = new BlockInserter(array(
117 'tableName' => static::getTableNameWord2Location(),
118 'exactFields' => array(
119 'LOCATION_ID' => array('data_type' => 'integer'),
120 'WORD_ID' => array('data_type' => 'integer')
121 ),
122 'parameters' => array(
123 'mtu' => static::MTU
124 )
125 ));
126
127 $this->dictionaryInserter = new BlockInserter(array(
128 'entityName' => '\Bitrix\Sale\Location\Search\WordTable',
129 'exactFields' => array(
130 'WORD'
131 ),
132 'parameters' => array(
133 'mtu' => static::MTU,
134 'autoIncrementFld' => 'ID',
135 'CALLBACKS' => array(
136 'ON_BEFORE_FLUSH' => array($this, 'onBeforeDictionaryFlush')
137 )
138 )
139 ));
140
141 $this->dictionaryResorter = new BlockInserter(array(
142 'tableName' => static::getTableNamePositionTemporal(),
143 'exactFields' => array(
144 'WORD_ID' => array('data_type' => 'integer'),
145 'POSITION' => array('data_type' => 'integer')
146 ),
147 'parameters' => array(
148 'mtu' => static::MTU
149 )
150 ));
151 }
152
153 public function resetProcess()
154 {
155 $this->procData = array(
156 'OFFSET' => 0,
157 'POSITION' => 0,
158 'CURRENT_LOCATION' => false,
159 'CURRENT_LOCATION_WORDS' => array()
160 );
161 }
162
163 public static function cleanUpData()
164 {
165 $dbConnection = Main\HttpApplication::getConnection();
166
167 Helper::dropTable(static::getTableName());
168
169 $binary = ToLower($dbConnection->getType()) == 'mysql' ? 'binary' : ''; // http://bugs.mysql.com/bug.php?id=34096
170
171 // ORACE: OK, MSSQL: OK
172 Main\HttpApplication::getConnection()->query("create table ".static::getTableName()." (
173
174 ID ".Helper::getSqlForDataType('int')." not null ".Helper::getSqlForAutoIncrement()." primary key,
175 WORD ".Helper::getSqlForDataType('varchar', 50)." ".$binary." not null,
176 POSITION ".Helper::getSqlForDataType('int')." default '0'
177 )");
178
179 Helper::addAutoIncrement(static::getTableName()); // only for ORACLE
180
181 Helper::createIndex(static::getTableName(), 'TMP', array('WORD'), true);
182 Helper::dropTable(static::getTableNameWord2Location());
183
184 // ORACLE: OK, MSSQL: OK
185 Main\HttpApplication::getConnection()->query("create table ".static::getTableNameWord2Location()." (
186
187 LOCATION_ID ".Helper::getSqlForDataType('int')." not null,
188 WORD_ID ".Helper::getSqlForDataType('int')." not null,
189
190 primary key (LOCATION_ID, WORD_ID)
191 )");
192
193 Helper::dropTable(static::getTableNamePositionTemporal());
194
195 $dbConnection->query("create table ".static::getTableNamePositionTemporal()." (
196 WORD_ID ".Helper::getSqlForDataType('int')." not null,
197 POSITION ".Helper::getSqlForDataType('int')." default '0'
198 )");
199 }
200
201 public static function createIndex()
202 {
203 Helper::dropIndexByName('IX_SALE_LOC_SEARCH_WORD_TMP', static::getTableName());
204 Helper::createIndex(static::getTableName(), 'WP', array('WORD', 'POSITION'));
205 }
206
207 public static function parseWords($words)
208 {
209 $result = array();
210 foreach($words as $k => &$word)
211 {
212 $word = ToUpper(trim($word));
213 $word = str_replace('%', '', $word);
214
215 if($word == '')
216 continue;
217
218 $result[] = $word;
219 }
220
221 //natsort($result);
222
223 return array_unique($result);
224 }
225
226 public static function parseString($query)
227 {
228 $query = ToUpper(Trim($query));
229
230 //$query = str_replace(array_keys(static::$blackList), static::$blackList, ' '.$query.' ');
231 $query = str_replace(array(')', '(', '%', '_'), array('', '', '', ''), $query);
232
233 $words = explode(' ', $query);
234
235 return self::parseWords($words);
236 }
237
238 public function setOffset($offset)
239 {
240 $this->procData['OFFSET'] = $offset;
241 }
242
243 public function getOffset()
244 {
245 return $this->procData['OFFSET'];
246 }
247
248 public function setPosition($position)
249 {
250 $this->procData['POSITION'] = $position;
251 }
252
253 public function getPosition()
254 {
255 return $this->procData['POSITION'];
256 }
257
258 public function onBeforeDictionaryFlush()
259 {
260 $this->dictionaryIndex = array();
261 }
262
263 public static function getFilterForInitData($parameters = array())
264 {
265 $filter = array();
266
267 if(!is_array($parameters))
268 $parameters = array();
269
270 if(is_array($parameters['TYPES']) && !empty($parameters['TYPES']))
271 $filter['=LOCATION.TYPE_ID'] = array_unique($parameters['TYPES']);
272
273 if(is_array($parameters['LANGS']) && !empty($parameters['LANGS']))
274 $filter['=LANGUAGE_ID'] = array_unique($parameters['LANGS']);
275
276 return $filter;
277 }
278
279 public function initializeData()
280 {
281 $res = Location\Name\LocationTable::getList(array(
282 'select' => array(
283 'NAME',
284 'LOCATION_ID'
285 ),
286 'filter' => static::getFilterForInitData(array(
287 'TYPES' => $this->procData['ALLOWED_TYPES'],
288 'LANGS' => $this->procData['ALLOWED_LANGS']
289 )),
290 'order' => array('LOCATION_ID' => 'asc'), // need to make same location ids stay together
291 'limit' => static::STEP_SIZE,
292 'offset' => $this->procData['OFFSET']
293 ));
294
295 $cnt = 0;
296 while($item = $res->fetch())
297 {
298 if($item['NAME'] <> '')
299 {
300 if($this->procData['CURRENT_LOCATION'] != $item['LOCATION_ID'])
301 {
302 $this->procData['CURRENT_LOCATION_WORDS'] = array();
303 }
304
305 $this->procData['CURRENT_LOCATION'] = $item['LOCATION_ID'];
306
307 $words = static::parseString($item['NAME']);
308
309 foreach($words as $k => &$word)
310 {
311 $wordHash = md5($word);
312
313 $wordId = false;
314 if(isset($this->dictionaryIndex[$wordHash])) // word is already added and in a hot index
315 {
316 $wordId = $this->dictionaryIndex[$wordHash];
317 }
318 else
319 {
320 $wordId = static::getIdByWord($word); // check if the word was added previously
321 }
322
323 if($wordId === false)
324 {
325 $wordId = $this->dictionaryInserter->insert(array(
326 'WORD' => $word
327 ));
328 $this->dictionaryIndex[$wordHash] = $wordId;
329 }
330
331 if($wordId !== false && !isset($this->procData['CURRENT_LOCATION_WORDS'][$wordId]))
332 {
333 $this->procData['CURRENT_LOCATION_WORDS'][$wordId] = true;
334
335 $this->word2LocationInserter->insert(array(
336 'LOCATION_ID' => intval($item['LOCATION_ID']),
337 'WORD_ID' => intval($wordId)
338 ));
339 }
340 }
341 }
342
343 $cnt++;
344 }
345
346 $this->procData['OFFSET'] += static::STEP_SIZE;
347
348 $this->dictionaryInserter->flush();
349 $this->word2LocationInserter->flush();
350
351 return !$cnt;
352 }
353
354 public function resort()
355 {
356 $res = Main\HttpApplication::getConnection()->query(
357 Main\HttpApplication::getConnection()->getSqlHelper()->getTopSql("select ID, WORD from ".static::getTableName()." order by WORD asc, ID asc", self::STEP_SIZE, intval($this->procData['OFFSET']))
358 );
359
360 $cnt = 0;
361 while($item = $res->fetch())
362 {
363 $this->procData['POSITION']++;
364
365 $this->dictionaryResorter->insert(array(
366 'WORD_ID' => $item['ID'],
367 'POSITION' => $this->procData['POSITION']
368 ));
369
370 $cnt++;
371 }
372
373 $this->procData['OFFSET'] += static::STEP_SIZE;
374
375 $this->dictionaryResorter->flush();
376
377 return !$cnt;
378 }
379
380 public static function mergeResort()
381 {
382 Helper::mergeTables(
383 static::getTableName(),
384 static::getTableNamePositionTemporal(),
385 array('POSITION' => 'POSITION'),
386 array('ID' => 'WORD_ID')
387 );
388
389 Main\HttpApplication::getConnection()->query("drop table ".static::getTableNamePositionTemporal());
390 }
391
392 public static function getIdByWord($word)
393 {
394 $word = trim((string)$word);
395 if ($word === '')
396 {
397 return false;
398 }
399
400 $dbConnection = Main\HttpApplication::getConnection();
401
402 $item = $dbConnection->query(
403 "select ID from " . static::getTableName()
404 . " where WORD = '" . $dbConnection->getSqlHelper()->forSql($word) . "'"
405 )->fetch();
406
407 $id = (int)($item['ID'] ?? 0);
408
409 return $id ?: false;
410 }
411
412 public static function getBoundsByWord($word)
413 {
414 $word = trim($word);
415
416 $dbConnection = Main\HttpApplication::getConnection();
417 $sql = "select MIN(POSITION) as INF, MAX(POSITION) as SUP from ".static::getTableName()." where WORD like '".ToUpper($dbConnection->getSqlHelper()->forSql($word))."%'";
418
419 return $dbConnection->query($sql)->fetch();
420 }
421
422 public static function getWordsByBounds($inf, $sup)
423 {
424 return static::getList(array('filter' => array(
425 '>=POSITION' => intval($inf),
426 '<=POSITION' => intval($sup)
427 ), 'order' => array(
428 'POSITION' => 'asc'
429 )));
430 }
431
432 public static function getBoundsForPhrase($phrase)
433 {
434 if(is_string($phrase))
435 $words = self::parseString($phrase);
436 elseif(is_array($phrase))
437 $words = self::parseWords($phrase);
438 else
439 return array();
440
441 // check for empty request
442
443 $bounds = array();
444 $sizes = array();
445 $i = 0;
446 foreach($words as $word)
447 {
448 $bound = self::getBoundsByWord($word);
449 if(!intval($bound['INF']) && !intval($bound['SUP'])) // no such word
450 return array();
451
452 $bounds[$i] = $bound;
453
454 $sizes[] = $bound['SUP'] - $bound['INF'];
455
456 $i++;
457 }
458
459 // resort bounds to have sorted smallest to largest
460 //asort($sizes, SORT_NUMERIC);
461
462 $boundsSorted = array();
463 foreach($sizes as $j => $size)
464 $boundsSorted[] = $bounds[$j];
465
466 // todo: here drop nested intervals, if any
467
468 return $boundsSorted;
469 }
470
471 public static function getMap()
472 {
473 return array(
474
475 'ID' => array(
476 'data_type' => 'integer',
477 'primary' => true
478 ),
479 'WORD' => array(
480 'data_type' => 'string',
481 ),
482 'POSITION' => array(
483 'data_type' => 'integer'
484 ),
485
486 'CNT' => array(
487 'data_type' => 'integer',
488 'expression' => array(
489 'count(*)'
490 )
491 ),
492 );
493 }
494}
495
static loadMessages($file)
Definition loc.php:64
static getWordsByBounds($inf, $sup)
Definition word.php:422
static getFilterForInitData($parameters=array())
Definition word.php:263