ILIAS  trunk Revision v11.0_alpha-1723-g8e69f309bab
All Data Structures Namespaces Files Functions Variables Enumerations Enumerator Modules Pages
DatabasePathsParser.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 
35 
37 {
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(
72  \ilDBInterface $db,
73  StructureSetInterface $structure,
74  DictionaryInterface $dictionary,
75  NavigatorFactoryInterface $navigator_factory,
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(
128  bool $force_join_to_base_table
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 
267  protected function getBaseJoinConditionsForTable(
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 
297  protected function getJoinConditionFromPathFilter(
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 }
$res
Definition: ltiservices.php:66
FilterType
Values should always be all lowercase.
Definition: FilterType.php:26
element()
Returns the element at the current step in the path.
$path
Definition: ltiservices.php:29
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
getBaseJoinConditionsForTable(string $first_table_alias, string $table_alias, ?string $parent_table_alias=null, ?string $parent_table=null, ?string $parent_type=null)
__construct(\ilDBInterface $db, StructureSetInterface $structure, DictionaryInterface $dictionary, NavigatorFactoryInterface $navigator_factory,)
StepToken
The string representation of these tokens must not occur as names of metadata elements.
Definition: StepToken.php:27
getDataColumn(string $quoted_table_alias, string $data_field)
Direct_data is only needed to make vocab sources work until controlled vocabularies are implemented...
addPathAndGetColumn(PathInterface $path, bool $force_join_to_base_table)
getSelectForQuery()
Make sure that you add paths before calling this.
getJoinConditionFromPathFilter(string $table_alias, string $table, string $data_field, PathFilter $filter)