ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
DatabasePathsParser.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
33use ILIAS\MetaData\Repository\Utilities\Queries\TableNamesHandler;
35
37{
38 use TableNamesHandler;
39
40 protected const string JOIN_TABLE = 'join_table';
41 protected const string JOIN_CONDITION = 'join_condition';
42 protected const string COLUMN_NAME = 'column_name';
43
47 protected array $path_joins_by_path = [];
48
52 protected array $additional_conditions_by_path = [];
53
54 protected int $path_number = 1;
55
59 protected array $columns_by_path = [];
60
61 protected bool $force_join_to_base_table = false;
62
66 protected \ilDBInterface $db;
70
71 public function __construct(
76 ) {
77 $this->db = $db;
78 $this->structure = $structure;
79 $this->dictionary = $dictionary;
80 $this->navigator_factory = $navigator_factory;
81 }
82
86 public function getSelectForQuery(): string
87 {
88 $from_expression = '';
89 $base_table = '';
90 if (empty($this->path_joins_by_path)) {
91 throw new \ilMDRepositoryException('No tables found for search.');
92 } elseif (
93 count($this->path_joins_by_path) === 1 &&
94 !$this->force_join_to_base_table
95 ) {
96 $base_table = 'p1t1';
97 $from_expression = array_values($this->path_joins_by_path)[0];
98 $path = array_keys($this->path_joins_by_path)[0];
99 if (isset($this->additional_conditions_by_path[$path])) {
100 $from_expression .= ' WHERE ' .
101 implode(' AND ', $this->additional_conditions_by_path[$path]);
102 }
103 } else {
104 $base_table = 'base';
105 $from_expression = 'il_meta_general AS base';
106 $path_number = 1;
107 foreach ($this->path_joins_by_path as $path => $join) {
108 $condition = $this->getBaseJoinConditionsForTable(
109 'base',
110 'p' . $path_number . 't1',
111 );
112 if (isset($this->additional_conditions_by_path[$path])) {
113 $condition .= ' AND ' .
114 implode(' AND ', $this->additional_conditions_by_path[$path]);
115 }
116 $from_expression .= ' LEFT JOIN (' . $join . ') ON ' . $condition;
117 $path_number++;
118 }
119 }
120
121 return 'SELECT ' . $this->quoteIdentifier($base_table) . '.rbac_id, ' .
122 $this->quoteIdentifier($base_table) . '.obj_id, ' .
123 $this->quoteIdentifier($base_table) . '.obj_type FROM ' . $from_expression;
124 }
125
126 public function addPathAndGetColumn(
129 ): string {
130 if (!$this->force_join_to_base_table) {
131 $this->force_join_to_base_table = $force_join_to_base_table;
132 }
133
134 $path_string = $path->toString();
135 if (isset($this->columns_by_path[$path_string])) {
136 return $this->columns_by_path[$path_string];
137 }
138
139 $data_column_name = '';
140
141 $tables = [];
142 $conditions = [];
143 foreach ($this->collectJoinInfos($path, $this->path_number) as $type => $info) {
144 if ($type === self::JOIN_TABLE && !empty($info)) {
145 $tables[] = $info;
146 }
147 if ($type === self::JOIN_CONDITION && !empty($info)) {
148 $conditions[] = $info;
149 }
150 if ($type === self::COLUMN_NAME && !empty($info)) {
151 $data_column_name = $info;
152 }
153 }
154
155 if (count($tables) === 1 && !empty($conditions)) {
156 $this->path_joins_by_path[$path_string] = $tables[0];
162 $this->additional_conditions_by_path[$path_string] = $conditions;
163 $this->path_number++;
164 } elseif (!empty($tables)) {
165 $join = implode(' JOIN ', $tables);
166 if (!empty($conditions)) {
167 $join .= ' ON ' . implode(' AND ', $conditions);
168 }
169 $this->path_joins_by_path[$path_string] = $join;
170 $this->path_number++;
171 }
172
173 return $this->columns_by_path[$path_string] = $data_column_name;
174 }
175
176 public function getTableAliasForFilters(): string
177 {
178 if (empty($this->path_joins_by_path)) {
179 throw new \ilMDRepositoryException('No tables found for search.');
180 } elseif (
181 count($this->path_joins_by_path) === 1 &&
182 !$this->force_join_to_base_table
183 ) {
184 return 'p1t1';
185 }
186 return 'base';
187 }
188
192 protected function collectJoinInfos(
194 int $path_number
195 ): \Generator {
196 $navigator = $this->getNavigatorForPath($path);
197 $table_aliases = [];
198 $current_tag = null;
199 $current_table = '';
200 $table_number = 1;
201
202 $depth = 0;
203 while ($navigator->hasNextStep()) {
204 if ($depth > 20) {
205 throw new \ilMDStructureException('LOM Structure is nested to deep.');
206 }
207
208 $navigator = $navigator->nextStep();
209 $current_tag = $this->getTagForCurrentStepOfNavigator($navigator);
210
211 if ($current_tag?->table() && $current_table !== $current_tag?->table()) {
212 $parent_table = $current_table;
213 $current_table = $current_tag->table();
214 $this->checkTable($current_table);
215
222 if ($navigator->currentStep()->name() === StepToken::SUPER) {
223 $alias = $table_aliases[$current_table];
224 } else {
225 $alias = 'p' . $path_number . 't' . $table_number;
226 $table_aliases[$current_table] = $alias;
227 $table_number++;
228
229 yield self::JOIN_TABLE => $this->quoteIdentifier($this->table($current_table)) .
230 ' AS ' . $this->quoteIdentifier($alias);
231 }
232
233 if (!$current_tag->hasParent()) {
234 yield self::JOIN_CONDITION => $this->getBaseJoinConditionsForTable(
235 'p' . $path_number . 't1',
236 $alias
237 );
238 } else {
239 yield self::JOIN_CONDITION => $this->getBaseJoinConditionsForTable(
240 'p' . $path_number . 't1',
241 $alias,
242 $table_aliases[$parent_table],
243 $parent_table,
244 $current_tag->parent()
245 );
246 }
247 }
248
249 foreach ($navigator->currentStep()->filters() as $filter) {
250 yield self::JOIN_CONDITION => $res = $this->getJoinConditionFromPathFilter(
251 $table_aliases[$current_table],
252 $current_table,
253 $current_tag?->hasData() ? $current_tag->dataField() : '',
254 $filter
255 );
256 }
257
258 $depth++;
259 }
260
261 yield self::COLUMN_NAME => $this->getDataColumn(
262 $this->quoteIdentifier($table_aliases[$current_table]),
263 $current_tag?->hasData() ? $current_tag->dataField() : ''
264 );
265 }
266
268 string $first_table_alias,
269 string $table_alias,
270 ?string $parent_table_alias = null,
271 ?string $parent_table = null,
272 ?string $parent_type = null
273 ): string {
274 $table_alias = $this->quoteIdentifier($table_alias);
275 $first_table_alias = $this->quoteIdentifier($first_table_alias);
276 $conditions = [];
277
278 if ($table_alias !== $first_table_alias) {
279 $conditions[] = $first_table_alias . '.rbac_id = ' . $table_alias . '.rbac_id';
280 $conditions[] = $first_table_alias . '.obj_id = ' . $table_alias . '.obj_id';
281 $conditions[] = $first_table_alias . '.obj_type = ' . $table_alias . '.obj_type';
282 }
283
284 if (!is_null($parent_table_alias) && !is_null($parent_table)) {
285 $parent_id_column = $parent_table_alias . '.' .
286 $this->quoteIdentifier($this->IDName($parent_table));
287 $conditions[] = $parent_id_column . ' = ' . $table_alias . '.parent_id';
288 }
289 if (!is_null($parent_type)) {
290 $conditions[] = $this->quoteText($parent_type) .
291 ' = ' . $table_alias . '.parent_type';
292 }
293
294 return implode(' AND ', $conditions);
295 }
296
298 string $table_alias,
299 string $table,
300 string $data_field,
301 PathFilter $filter
302 ): string {
303 $table_alias = $this->quoteIdentifier($table_alias);
304 $quoted_values = [];
305 foreach ($filter->values() as $value) {
306 $quoted_values[] = $filter->type() === FilterType::DATA ?
307 $this->quoteText($value) :
308 $this->quoteInteger((int) $value);
309 }
310
311 if (empty($quoted_values)) {
312 return '';
313 }
314
315 switch ($filter->type()) {
316 case FilterType::NULL:
317 return '';
318
319 case FilterType::MDID:
320 $column = $table_alias . '.' . $this->quoteIdentifier($this->IDName($table));
321 return $column . ' IN (' . implode(', ', $quoted_values) . ')';
322 break;
323
324 case FilterType::INDEX:
325 // not supported
326 return '';
327
328 case FilterType::DATA:
329 $column = $this->getDataColumn($table_alias, $data_field);
330 return $column . ' IN (' . implode(', ', $quoted_values) . ')';
331 break;
332
333 default:
334 throw new \ilMDRepositoryException('Unknown filter type: ' . $filter->type()->value);
335 }
336 }
337
342 protected function getDataColumn(
343 string $quoted_table_alias,
344 string $data_field
345 ): string {
346 $column = $this->quoteText('');
347 if ($data_field !== '') {
348 $column = 'COALESCE(' . $quoted_table_alias . '.' . $this->quoteIdentifier($data_field) . ", '')";
349 }
350 return $column;
351 }
352
354 {
355 return $this->navigator_factory->structureNavigator(
356 $path,
357 $this->structure->getRoot()
358 );
359 }
360
362 {
363 return $this->dictionary->tagForElement($navigator->element());
364 }
365
367 {
368 return $navigator->element()->getDefinition()->dataType();
369 }
370
371 protected function quoteIdentifier(string $identifier): string
372 {
373 return $this->db->quoteIdentifier($identifier);
374 }
375
376 protected function quoteText(string $text): string
377 {
378 return $this->db->quote($text, \ilDBConstants::T_TEXT);
379 }
380
381 protected function quoteInteger(int $integer): string
382 {
383 return $this->db->quote($integer, \ilDBConstants::T_INTEGER);
384 }
385}
__construct(\ilDBInterface $db, StructureSetInterface $structure, DictionaryInterface $dictionary, NavigatorFactoryInterface $navigator_factory,)
getJoinConditionFromPathFilter(string $table_alias, string $table, string $data_field, PathFilter $filter)
getDataColumn(string $quoted_table_alias, string $data_field)
Direct_data is only needed to make vocab sources work until controlled vocabularies are implemented.
getBaseJoinConditionsForTable(string $first_table_alias, string $table_alias, ?string $parent_table_alias=null, ?string $parent_table=null, ?string $parent_type=null)
addPathAndGetColumn(PathInterface $path, bool $force_join_to_base_table)
getSelectForQuery()
Make sure that you add paths before calling this.
$info
Definition: entry_point.php:21
element()
Returns the element at the current step in the path.
Interface ilDBInterface.
$path
Definition: ltiservices.php:30
$res
Definition: ltiservices.php:69
FilterType
Values should always be all lowercase.
Definition: FilterType.php:27
StepToken
The string representation of these tokens must not occur as names of metadata elements.
Definition: StepToken.php:28
if(!file_exists('../ilias.ini.php'))