7df6521b1cbb5529f419f4954197c5b871d79ff1
[yaffs-website] / web / core / modules / views / src / Plugin / views / query / Sql.php
1 <?php
2
3 namespace Drupal\views\Plugin\views\query;
4
5 use Drupal\Component\Utility\NestedArray;
6 use Drupal\Core\Cache\Cache;
7 use Drupal\Core\Database\Database;
8 use Drupal\Core\Database\Query\Condition;
9 use Drupal\Core\Entity\EntityTypeManagerInterface;
10 use Drupal\Core\Form\FormStateInterface;
11 use Drupal\views\Plugin\views\display\DisplayPluginBase;
12 use Drupal\Core\Database\DatabaseExceptionWrapper;
13 use Drupal\views\Plugin\views\join\JoinPluginBase;
14 use Drupal\views\Plugin\views\HandlerBase;
15 use Drupal\views\ResultRow;
16 use Drupal\views\ViewExecutable;
17 use Drupal\views\Views;
18 use Symfony\Component\DependencyInjection\ContainerInterface;
19
20 /**
21  * Views query plugin for an SQL query.
22  *
23  * @ingroup views_query_plugins
24  *
25  * @ViewsQuery(
26  *   id = "views_query",
27  *   title = @Translation("SQL Query"),
28  *   help = @Translation("Query will be generated and run using the Drupal database API.")
29  * )
30  */
31 class Sql extends QueryPluginBase {
32
33   /**
34    * A list of tables in the order they should be added, keyed by alias.
35    */
36   protected $tableQueue = [];
37
38   /**
39    * Holds an array of tables and counts added so that we can create aliases
40    */
41   public $tables = [];
42
43   /**
44    * Holds an array of relationships, which are aliases of the primary
45    * table that represent different ways to join the same table in.
46    */
47   public $relationships = [];
48
49   /**
50    * An array of sections of the WHERE query. Each section is in itself
51    * an array of pieces and a flag as to whether or not it should be AND
52    * or OR.
53    */
54   public $where = [];
55   /**
56    * An array of sections of the HAVING query. Each section is in itself
57    * an array of pieces and a flag as to whether or not it should be AND
58    * or OR.
59    */
60   public $having = [];
61   /**
62    * The default operator to use when connecting the WHERE groups. May be
63    * AND or OR.
64    *
65    * @var string
66    */
67   protected $groupOperator = 'AND';
68
69   /**
70    * A simple array of order by clauses.
71    */
72   public $orderby = [];
73
74   /**
75    * A simple array of group by clauses.
76    */
77   public $groupby = [];
78
79
80   /**
81    * An array of fields.
82    */
83   public $fields = [];
84
85   /**
86    * A flag as to whether or not to make the primary field distinct.
87    *
88    * @var bool
89    */
90   public $distinct = FALSE;
91
92   /**
93    * @var bool
94    */
95   protected $hasAggregate = FALSE;
96
97   /**
98    * Should this query be optimized for counts, for example no sorts.
99    */
100   protected $getCountOptimized = NULL;
101
102   /**
103    * An array mapping table aliases and field names to field aliases.
104    */
105   protected $fieldAliases = [];
106
107   /**
108    * Query tags which will be passed over to the dbtng query object.
109    */
110   public $tags = [];
111
112   /**
113    * Is the view marked as not distinct.
114    *
115    * @var bool
116    */
117   protected $noDistinct;
118
119   /**
120    * The entity type manager.
121    *
122    * @var \Drupal\Core\Entity\EntityTypeManagerInterface
123    */
124   protected $entityTypeManager;
125
126   /**
127    * Constructs a Sql object.
128    *
129    * @param array $configuration
130    *   A configuration array containing information about the plugin instance.
131    * @param string $plugin_id
132    *   The plugin_id for the plugin instance.
133    * @param mixed $plugin_definition
134    *   The plugin implementation definition.
135    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
136    *   The entity type manager.
137    */
138   public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) {
139     parent::__construct($configuration, $plugin_id, $plugin_definition);
140
141     $this->entityTypeManager = $entity_type_manager;
142   }
143
144   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
145     return new static(
146       $configuration,
147       $plugin_id,
148       $plugin_definition,
149       $container->get('entity_type.manager')
150     );
151   }
152
153   /**
154    * {@inheritdoc}
155    */
156   public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
157     parent::init($view, $display, $options);
158
159     $base_table = $this->view->storage->get('base_table');
160     $base_field = $this->view->storage->get('base_field');
161     $this->relationships[$base_table] = [
162       'link' => NULL,
163       'table' => $base_table,
164       'alias' => $base_table,
165       'base' => $base_table
166     ];
167
168     // init the table queue with our primary table.
169     $this->tableQueue[$base_table] = [
170       'alias' => $base_table,
171       'table' => $base_table,
172       'relationship' => $base_table,
173       'join' => NULL,
174     ];
175
176     // init the tables with our primary table
177     $this->tables[$base_table][$base_table] = [
178       'count' => 1,
179       'alias' => $base_table,
180     ];
181
182     $this->count_field = [
183       'table' => $base_table,
184       'field' => $base_field,
185       'alias' => $base_field,
186       'count' => TRUE,
187     ];
188   }
189
190   /**
191    * Set the view to be distinct (per base field).
192    *
193    * @param bool $value
194    *   Should the view be distincted.
195    */
196   protected function setDistinct($value = TRUE) {
197     if (!(isset($this->noDistinct) && $value)) {
198       $this->distinct = $value;
199     }
200   }
201
202   /**
203    * Set what field the query will count() on for paging.
204    */
205   public function setCountField($table, $field, $alias = NULL) {
206     if (empty($alias)) {
207       $alias = $table . '_' . $field;
208     }
209     $this->count_field = [
210       'table' => $table,
211       'field' => $field,
212       'alias' => $alias,
213       'count' => TRUE,
214     ];
215   }
216
217   protected function defineOptions() {
218     $options = parent::defineOptions();
219     $options['disable_sql_rewrite'] = [
220       'default' => FALSE,
221     ];
222     $options['distinct'] = [
223       'default' => FALSE,
224     ];
225     $options['replica'] = [
226       'default' => FALSE,
227     ];
228     $options['query_comment'] = [
229       'default' => '',
230     ];
231     $options['query_tags'] = [
232       'default' => [],
233     ];
234
235     return $options;
236   }
237
238   /**
239    * Add settings for the ui.
240    */
241   public function buildOptionsForm(&$form, FormStateInterface $form_state) {
242     parent::buildOptionsForm($form, $form_state);
243
244     $form['disable_sql_rewrite'] = [
245       '#title' => $this->t('Disable SQL rewriting'),
246       '#description' => $this->t('Disabling SQL rewriting will omit all query tags, i. e. disable node access checks as well as override hook_query_alter() implementations in other modules.'),
247       '#type' => 'checkbox',
248       '#default_value' => !empty($this->options['disable_sql_rewrite']),
249       '#suffix' => '<div class="messages messages--warning sql-rewrite-warning js-hide">' . $this->t('WARNING: Disabling SQL rewriting means that node access security is disabled. This may allow users to see data they should not be able to see if your view is misconfigured. Use this option only if you understand and accept this security risk.') . '</div>',
250     ];
251     $form['distinct'] = [
252       '#type' => 'checkbox',
253       '#title' => $this->t('Distinct'),
254       '#description' => $this->t('This will make the view display only distinct items. If there are multiple identical items, each will be displayed only once. You can use this to try and remove duplicates from a view, though it does not always work. Note that this can slow queries down, so use it with caution.'),
255       '#default_value' => !empty($this->options['distinct']),
256     ];
257     $form['replica'] = [
258       '#type' => 'checkbox',
259       '#title' => $this->t('Use Secondary Server'),
260       '#description' => $this->t('This will make the query attempt to connect to a replica server if available.  If no replica server is defined or available, it will fall back to the default server.'),
261       '#default_value' => !empty($this->options['replica']),
262     ];
263     $form['query_comment'] = [
264       '#type' => 'textfield',
265       '#title' => $this->t('Query Comment'),
266       '#description' => $this->t('If set, this comment will be embedded in the query and passed to the SQL server. This can be helpful for logging or debugging.'),
267       '#default_value' => $this->options['query_comment'],
268     ];
269     $form['query_tags'] = [
270       '#type' => 'textfield',
271       '#title' => $this->t('Query Tags'),
272       '#description' => $this->t('If set, these tags will be appended to the query and can be used to identify the query in a module. This can be helpful for altering queries.'),
273       '#default_value' => implode(', ', $this->options['query_tags']),
274       '#element_validate' => ['views_element_validate_tags'],
275     ];
276   }
277
278   /**
279    * Special submit handling.
280    */
281   public function submitOptionsForm(&$form, FormStateInterface $form_state) {
282     $element = ['#parents' => ['query', 'options', 'query_tags']];
283     $value = explode(',', NestedArray::getValue($form_state->getValues(), $element['#parents']));
284     $value = array_filter(array_map('trim', $value));
285     $form_state->setValueForElement($element, $value);
286   }
287
288   /**
289    * A relationship is an alternative endpoint to a series of table
290    * joins. Relationships must be aliases of the primary table and
291    * they must join either to the primary table or to a pre-existing
292    * relationship.
293    *
294    * An example of a relationship would be a nodereference table.
295    * If you have a nodereference named 'book_parent' which links to a
296    * parent node, you could set up a relationship 'node_book_parent'
297    * to 'node'. Then, anything that links to 'node' can link to
298    * 'node_book_parent' instead, thus allowing all properties of
299    * both nodes to be available in the query.
300    *
301    * @param $alias
302    *   What this relationship will be called, and is also the alias
303    *   for the table.
304    * @param \Drupal\views\Plugin\views\join\JoinPluginBase $join
305    *   A Join object (or derived object) to join the alias in.
306    * @param $base
307    *   The name of the 'base' table this relationship represents; this
308    *   tells the join search which path to attempt to use when finding
309    *   the path to this relationship.
310    * @param $link_point
311    *   If this relationship links to something other than the primary
312    *   table, specify that table here. For example, a 'track' node
313    *   might have a relationship to an 'album' node, which might
314    *   have a relationship to an 'artist' node.
315    */
316   public function addRelationship($alias, JoinPluginBase $join, $base, $link_point = NULL) {
317     if (empty($link_point)) {
318       $link_point = $this->view->storage->get('base_table');
319     }
320     elseif (!array_key_exists($link_point, $this->relationships)) {
321       return FALSE;
322     }
323
324     // Make sure $alias isn't already used; if it, start adding stuff.
325     $alias_base = $alias;
326     $count = 1;
327     while (!empty($this->relationships[$alias])) {
328       $alias = $alias_base . '_' . $count++;
329     }
330
331     // Make sure this join is adjusted for our relationship.
332     if ($link_point && isset($this->relationships[$link_point])) {
333       $join = $this->adjustJoin($join, $link_point);
334     }
335
336     // Add the table directly to the queue to avoid accidentally marking
337     // it.
338     $this->tableQueue[$alias] = [
339       'table' => $join->table,
340       'num' => 1,
341       'alias' => $alias,
342       'join' => $join,
343       'relationship' => $link_point,
344     ];
345
346     $this->relationships[$alias] = [
347       'link' => $link_point,
348       'table' => $join->table,
349       'base' => $base,
350     ];
351
352     $this->tables[$this->view->storage->get('base_table')][$alias] = [
353       'count' => 1,
354       'alias' => $alias,
355     ];
356
357     return $alias;
358   }
359
360   /**
361    * Add a table to the query, ensuring the path exists.
362    *
363    * This function will test to ensure that the path back to the primary
364    * table is valid and exists; if you do not wish for this testing to
365    * occur, use $query->queueTable() instead.
366    *
367    * @param $table
368    *   The name of the table to add. It needs to exist in the global table
369    *   array.
370    * @param $relationship
371    *   An alias of a table; if this is set, the path back to this table will
372    *   be tested prior to adding the table, making sure that all intermediary
373    *   tables exist and are properly aliased. If set to NULL the path to
374    *   the primary table will be ensured. If the path cannot be made, the
375    *   table will NOT be added.
376    * @param \Drupal\views\Plugin\views\join\JoinPluginBase $join
377    *   In some join configurations this table may actually join back through
378    *   a different method; this is most likely to be used when tracing
379    *   a hierarchy path. (node->parent->parent2->parent3). This parameter
380    *   will specify how this table joins if it is not the default.
381    * @param $alias
382    *   A specific alias to use, rather than the default alias.
383    *
384    * @return string
385    *   The alias of the table; this alias can be used to access information
386    *   about the table and should always be used to refer to the table when
387    *   adding parts to the query. Or FALSE if the table was not able to be
388    *   added.
389    */
390   public function addTable($table, $relationship = NULL, JoinPluginBase $join = NULL, $alias = NULL) {
391     if (!$this->ensurePath($table, $relationship, $join)) {
392       return FALSE;
393     }
394
395     if ($join && $relationship) {
396       $join = $this->adjustJoin($join, $relationship);
397     }
398
399     return $this->queueTable($table, $relationship, $join, $alias);
400   }
401
402   /**
403    * Add a table to the query without ensuring the path.
404    *
405    * This is a pretty internal function to Views and addTable() or
406    * ensureTable() should be used instead of this one, unless you are
407    * absolutely sure this is what you want.
408    *
409    * @param $table
410    *   The name of the table to add. It needs to exist in the global table
411    *   array.
412    * @param $relationship
413    *   The primary table alias this table is related to. If not set, the
414    *   primary table will be used.
415    * @param \Drupal\views\Plugin\views\join\JoinPluginBase $join
416    *   In some join configurations this table may actually join back through
417    *   a different method; this is most likely to be used when tracing
418    *   a hierarchy path. (node->parent->parent2->parent3). This parameter
419    *   will specify how this table joins if it is not the default.
420    * @param $alias
421    *   A specific alias to use, rather than the default alias.
422    *
423    * @return string
424    *   The alias of the table; this alias can be used to access information
425    *   about the table and should always be used to refer to the table when
426    *   adding parts to the query. Or FALSE if the table was not able to be
427    *   added.
428    */
429   public function queueTable($table, $relationship = NULL, JoinPluginBase $join = NULL, $alias = NULL) {
430     // If the alias is set, make sure it doesn't already exist.
431     if (isset($this->tableQueue[$alias])) {
432       return $alias;
433     }
434
435     if (empty($relationship)) {
436       $relationship = $this->view->storage->get('base_table');
437     }
438
439     if (!array_key_exists($relationship, $this->relationships)) {
440       return FALSE;
441     }
442
443     if (!$alias && $join && $relationship && !empty($join->adjusted) && $table != $join->table) {
444       if ($relationship == $this->view->storage->get('base_table')) {
445         $alias = $table;
446       }
447       else {
448         $alias = $relationship . '_' . $table;
449       }
450     }
451
452     // Check this again to make sure we don't blow up existing aliases for already
453     // adjusted joins.
454     if (isset($this->tableQueue[$alias])) {
455       return $alias;
456     }
457
458     $alias = $this->markTable($table, $relationship, $alias);
459
460     // If no alias is specified, give it the default.
461     if (!isset($alias)) {
462       $alias = $this->tables[$relationship][$table]['alias'] . $this->tables[$relationship][$table]['count'];
463     }
464
465     // If this is a relationship based table, add a marker with
466     // the relationship as a primary table for the alias.
467     if ($table != $alias) {
468       $this->markTable($alias, $this->view->storage->get('base_table'), $alias);
469     }
470
471     // If no join is specified, pull it from the table data.
472     if (!isset($join)) {
473       $join = $this->getJoinData($table, $this->relationships[$relationship]['base']);
474       if (empty($join)) {
475         return FALSE;
476       }
477
478       $join = $this->adjustJoin($join, $relationship);
479     }
480
481     $this->tableQueue[$alias] = [
482       'table' => $table,
483       'num' => $this->tables[$relationship][$table]['count'],
484       'alias' => $alias,
485       'join' => $join,
486       'relationship' => $relationship,
487     ];
488
489     return $alias;
490   }
491
492   protected function markTable($table, $relationship, $alias) {
493     // Mark that this table has been added.
494     if (empty($this->tables[$relationship][$table])) {
495       if (!isset($alias)) {
496         $alias = '';
497         if ($relationship != $this->view->storage->get('base_table')) {
498           // double underscore will help prevent accidental name
499           // space collisions.
500           $alias = $relationship . '__';
501         }
502         $alias .= $table;
503       }
504       $this->tables[$relationship][$table] = [
505         'count' => 1,
506         'alias' => $alias,
507       ];
508     }
509     else {
510       $this->tables[$relationship][$table]['count']++;
511     }
512
513     return $alias;
514   }
515
516   /**
517    * Ensure a table exists in the queue; if it already exists it won't
518    * do anything, but if it doesn't it will add the table queue. It will ensure
519    * a path leads back to the relationship table.
520    *
521    * @param $table
522    *   The unaliased name of the table to ensure.
523    * @param $relationship
524    *   The relationship to ensure the table links to. Each relationship will
525    *   get a unique instance of the table being added. If not specified,
526    *   will be the primary table.
527    * @param \Drupal\views\Plugin\views\join\JoinPluginBase $join
528    *   A Join object (or derived object) to join the alias in.
529    *
530    * @return
531    *   The alias used to refer to this specific table, or NULL if the table
532    *   cannot be ensured.
533    */
534   public function ensureTable($table, $relationship = NULL, JoinPluginBase $join = NULL) {
535     // ensure a relationship
536     if (empty($relationship)) {
537       $relationship = $this->view->storage->get('base_table');
538     }
539
540     // If the relationship is the primary table, this actually be a relationship
541     // link back from an alias. We store all aliases along with the primary table
542     // to detect this state, because eventually it'll hit a table we already
543     // have and that's when we want to stop.
544     if ($relationship == $this->view->storage->get('base_table') && !empty($this->tables[$relationship][$table])) {
545       return $this->tables[$relationship][$table]['alias'];
546     }
547
548     if (!array_key_exists($relationship, $this->relationships)) {
549       return FALSE;
550     }
551
552     if ($table == $this->relationships[$relationship]['base']) {
553       return $relationship;
554     }
555
556     // If we do not have join info, fetch it.
557     if (!isset($join)) {
558       $join = $this->getJoinData($table, $this->relationships[$relationship]['base']);
559     }
560
561     // If it can't be fetched, this won't work.
562     if (empty($join)) {
563       return;
564     }
565
566     // Adjust this join for the relationship, which will ensure that the 'base'
567     // table it links to is correct. Tables adjoined to a relationship
568     // join to a link point, not the base table.
569     $join = $this->adjustJoin($join, $relationship);
570
571     if ($this->ensurePath($table, $relationship, $join)) {
572       // Attempt to eliminate redundant joins.  If this table's
573       // relationship and join exactly matches an existing table's
574       // relationship and join, we do not have to join to it again;
575       // just return the existing table's alias.  See
576       // http://groups.drupal.org/node/11288 for details.
577       //
578       // This can be done safely here but not lower down in
579       // queueTable(), because queueTable() is also used by
580       // addTable() which requires the ability to intentionally add
581       // the same table with the same join multiple times.  For
582       // example, a view that filters on 3 taxonomy terms using AND
583       // needs to join taxonomy_term_data 3 times with the same join.
584
585       // scan through the table queue to see if a matching join and
586       // relationship exists.  If so, use it instead of this join.
587
588       // TODO: Scanning through $this->tableQueue results in an
589       // O(N^2) algorithm, and this code runs every time the view is
590       // instantiated (Views 2 does not currently cache queries).
591       // There are a couple possible "improvements" but we should do
592       // some performance testing before picking one.
593       foreach ($this->tableQueue as $queued_table) {
594         // In PHP 4 and 5, the == operation returns TRUE for two objects
595         // if they are instances of the same class and have the same
596         // attributes and values.
597         if ($queued_table['relationship'] == $relationship && $queued_table['join'] == $join) {
598           return $queued_table['alias'];
599         }
600       }
601
602       return $this->queueTable($table, $relationship, $join);
603     }
604   }
605
606   /**
607    * Make sure that the specified table can be properly linked to the primary
608    * table in the JOINs. This function uses recursion. If the tables
609    * needed to complete the path back to the primary table are not in the
610    * query they will be added, but additional copies will NOT be added
611    * if the table is already there.
612    */
613   protected function ensurePath($table, $relationship = NULL, $join = NULL, $traced = [], $add = []) {
614     if (!isset($relationship)) {
615       $relationship = $this->view->storage->get('base_table');
616     }
617
618     if (!array_key_exists($relationship, $this->relationships)) {
619       return FALSE;
620     }
621
622     // If we do not have join info, fetch it.
623     if (!isset($join)) {
624       $join = $this->getJoinData($table, $this->relationships[$relationship]['base']);
625     }
626
627     // If it can't be fetched, this won't work.
628     if (empty($join)) {
629       return FALSE;
630     }
631
632     // Does a table along this path exist?
633     if (isset($this->tables[$relationship][$table]) ||
634       ($join && $join->leftTable == $relationship) ||
635       ($join && $join->leftTable == $this->relationships[$relationship]['table'])) {
636
637       // Make sure that we're linking to the correct table for our relationship.
638       foreach (array_reverse($add) as $table => $path_join) {
639         $this->queueTable($table, $relationship, $this->adjustJoin($path_join, $relationship));
640       }
641       return TRUE;
642     }
643
644     // Have we been this way?
645     if (isset($traced[$join->leftTable])) {
646       // We looped. Broken.
647       return FALSE;
648     }
649
650     // Do we have to add this table?
651     $left_join = $this->getJoinData($join->leftTable, $this->relationships[$relationship]['base']);
652     if (!isset($this->tables[$relationship][$join->leftTable])) {
653       $add[$join->leftTable] = $left_join;
654     }
655
656     // Keep looking.
657     $traced[$join->leftTable] = TRUE;
658     return $this->ensurePath($join->leftTable, $relationship, $left_join, $traced, $add);
659   }
660
661   /**
662    * Fix a join to adhere to the proper relationship; the left table can vary
663    * based upon what relationship items are joined in on.
664    */
665   protected function adjustJoin($join, $relationship) {
666     if (!empty($join->adjusted)) {
667       return $join;
668     }
669
670     if (empty($relationship) || empty($this->relationships[$relationship])) {
671       return $join;
672     }
673
674     // Adjusts the left table for our relationship.
675     if ($relationship != $this->view->storage->get('base_table')) {
676       // If we're linking to the primary table, the relationship to use will
677       // be the prior relationship. Unless it's a direct link.
678
679       // Safety! Don't modify an original here.
680       $join = clone $join;
681
682       // Do we need to try to ensure a path?
683       if ($join->leftTable != $this->relationships[$relationship]['table'] &&
684         $join->leftTable != $this->relationships[$relationship]['base'] &&
685         !isset($this->tables[$relationship][$join->leftTable]['alias'])) {
686         $this->ensureTable($join->leftTable, $relationship);
687       }
688
689       // First, if this is our link point/anchor table, just use the relationship
690       if ($join->leftTable == $this->relationships[$relationship]['table']) {
691         $join->leftTable = $relationship;
692       }
693       // then, try the base alias.
694       elseif (isset($this->tables[$relationship][$join->leftTable]['alias'])) {
695         $join->leftTable = $this->tables[$relationship][$join->leftTable]['alias'];
696       }
697       // But if we're already looking at an alias, use that instead.
698       elseif (isset($this->tableQueue[$relationship]['alias'])) {
699         $join->leftTable = $this->tableQueue[$relationship]['alias'];
700       }
701     }
702
703     $join->adjusted = TRUE;
704     return $join;
705   }
706
707   /**
708    * Retrieve join data from the larger join data cache.
709    *
710    * @param $table
711    *   The table to get the join information for.
712    * @param $base_table
713    *   The path we're following to get this join.
714    *
715    * @return \Drupal\views\Plugin\views\join\JoinPluginBase
716    *   A Join object or child object, if one exists.
717    */
718   public function getJoinData($table, $base_table) {
719     // Check to see if we're linking to a known alias. If so, get the real
720     // table's data instead.
721     if (!empty($this->tableQueue[$table])) {
722       $table = $this->tableQueue[$table]['table'];
723     }
724     return HandlerBase::getTableJoin($table, $base_table);
725   }
726
727   /**
728    * Get the information associated with a table.
729    *
730    * If you need the alias of a table with a particular relationship, use
731    * ensureTable().
732    */
733   public function getTableInfo($table) {
734     if (!empty($this->tableQueue[$table])) {
735       return $this->tableQueue[$table];
736     }
737
738     // In rare cases we might *only* have aliased versions of the table.
739     if (!empty($this->tables[$this->view->storage->get('base_table')][$table])) {
740       $alias = $this->tables[$this->view->storage->get('base_table')][$table]['alias'];
741       if (!empty($this->tableQueue[$alias])) {
742         return $this->tableQueue[$alias];
743       }
744     }
745   }
746
747   /**
748    * Add a field to the query table, possibly with an alias. This will
749    * automatically call ensureTable to make sure the required table
750    * exists, *unless* $table is unset.
751    *
752    * @param $table
753    *   The table this field is attached to. If NULL, it is assumed this will
754    *   be a formula; otherwise, ensureTable is used to make sure the
755    *   table exists.
756    * @param $field
757    *   The name of the field to add. This may be a real field or a formula.
758    * @param $alias
759    *   The alias to create. If not specified, the alias will be $table_$field
760    *   unless $table is NULL. When adding formulae, it is recommended that an
761    *   alias be used.
762    * @param $params
763    *   An array of parameters additional to the field that will control items
764    *   such as aggregation functions and DISTINCT. Some values that are
765    *   recognized:
766    *   - function: An aggregation function to apply, such as SUM.
767    *   - aggregate: Set to TRUE to indicate that this value should be
768    *     aggregated in a GROUP BY.
769    *
770    * @return string
771    *   The name that this field can be referred to as. Usually this is the alias.
772    */
773   public function addField($table, $field, $alias = '', $params = []) {
774     // We check for this specifically because it gets a special alias.
775     if ($table == $this->view->storage->get('base_table') && $field == $this->view->storage->get('base_field') && empty($alias)) {
776       $alias = $this->view->storage->get('base_field');
777     }
778
779     if ($table && empty($this->tableQueue[$table])) {
780       $this->ensureTable($table);
781     }
782
783     if (!$alias && $table) {
784       $alias = $table . '_' . $field;
785     }
786
787     // Make sure an alias is assigned
788     $alias = $alias ? $alias : $field;
789
790     // PostgreSQL truncates aliases to 63 characters:
791     //   https://www.drupal.org/node/571548.
792
793     // We limit the length of the original alias up to 60 characters
794     // to get a unique alias later if its have duplicates
795     $alias = strtolower(substr($alias, 0, 60));
796
797     // Create a field info array.
798     $field_info = [
799       'field' => $field,
800       'table' => $table,
801       'alias' => $alias,
802     ] + $params;
803
804     // Test to see if the field is actually the same or not. Due to
805     // differing parameters changing the aggregation function, we need
806     // to do some automatic alias collision detection:
807     $base = $alias;
808     $counter = 0;
809     while (!empty($this->fields[$alias]) && $this->fields[$alias] != $field_info) {
810       $field_info['alias'] = $alias = $base . '_' . ++$counter;
811     }
812
813     if (empty($this->fields[$alias])) {
814       $this->fields[$alias] = $field_info;
815     }
816
817     // Keep track of all aliases used.
818     $this->fieldAliases[$table][$field] = $alias;
819
820     return $alias;
821   }
822
823   /**
824    * Remove all fields that may've been added; primarily used for summary
825    * mode where we're changing the query because we didn't get data we needed.
826    */
827   public function clearFields() {
828     $this->fields = [];
829   }
830
831   /**
832    * Add a simple WHERE clause to the query. The caller is responsible for
833    * ensuring that all fields are fully qualified (TABLE.FIELD) and that
834    * the table already exists in the query.
835    *
836    * The $field, $value and $operator arguments can also be passed in with a
837    * single DatabaseCondition object, like this:
838    * @code
839    * $this->query->addWhere(
840    *   $this->options['group'],
841    *   (new Condition('OR'))
842    *     ->condition($field, $value, 'NOT IN')
843    *     ->condition($field, $value, 'IS NULL')
844    * );
845    * @endcode
846    *
847    * @param $group
848    *   The WHERE group to add these to; groups are used to create AND/OR
849    *   sections. Groups cannot be nested. Use 0 as the default group.
850    *   If the group does not yet exist it will be created as an AND group.
851    * @param $field
852    *   The name of the field to check.
853    * @param $value
854    *   The value to test the field against. In most cases, this is a scalar. For more
855    *   complex options, it is an array. The meaning of each element in the array is
856    *   dependent on the $operator.
857    * @param $operator
858    *   The comparison operator, such as =, <, or >=. It also accepts more
859    *   complex options such as IN, LIKE, LIKE BINARY, or BETWEEN. Defaults to =.
860    *   If $field is a string you have to use 'formula' here.
861    *
862    * @see \Drupal\Core\Database\Query\ConditionInterface::condition()
863    * @see \Drupal\Core\Database\Query\Condition
864    */
865   public function addWhere($group, $field, $value = NULL, $operator = NULL) {
866     // Ensure all variants of 0 are actually 0. Thus '', 0 and NULL are all
867     // the default group.
868     if (empty($group)) {
869       $group = 0;
870     }
871
872     // Check for a group.
873     if (!isset($this->where[$group])) {
874       $this->setWhereGroup('AND', $group);
875     }
876
877     $this->where[$group]['conditions'][] = [
878       'field' => $field,
879       'value' => $value,
880       'operator' => $operator,
881     ];
882   }
883
884   /**
885    * Add a complex WHERE clause to the query.
886    *
887    * The caller is responsible for ensuring that all fields are fully qualified
888    * (TABLE.FIELD) and that the table already exists in the query.
889    * Internally the dbtng method "where" is used.
890    *
891    * @param $group
892    *   The WHERE group to add these to; groups are used to create AND/OR
893    *   sections. Groups cannot be nested. Use 0 as the default group.
894    *   If the group does not yet exist it will be created as an AND group.
895    * @param $snippet
896    *   The snippet to check. This can be either a column or
897    *   a complex expression like "UPPER(table.field) = 'value'"
898    * @param $args
899    *   An associative array of arguments.
900    *
901    * @see QueryConditionInterface::where()
902    */
903   public function addWhereExpression($group, $snippet, $args = []) {
904     // Ensure all variants of 0 are actually 0. Thus '', 0 and NULL are all
905     // the default group.
906     if (empty($group)) {
907       $group = 0;
908     }
909
910     // Check for a group.
911     if (!isset($this->where[$group])) {
912       $this->setWhereGroup('AND', $group);
913     }
914
915     $this->where[$group]['conditions'][] = [
916       'field' => $snippet,
917       'value' => $args,
918       'operator' => 'formula',
919     ];
920   }
921
922   /**
923    * Add a complex HAVING clause to the query.
924    * The caller is responsible for ensuring that all fields are fully qualified
925    * (TABLE.FIELD) and that the table and an appropriate GROUP BY already exist in the query.
926    * Internally the dbtng method "having" is used.
927    *
928    * @param $group
929    *   The HAVING group to add these to; groups are used to create AND/OR
930    *   sections. Groups cannot be nested. Use 0 as the default group.
931    *   If the group does not yet exist it will be created as an AND group.
932    * @param $snippet
933    *   The snippet to check. This can be either a column or
934    *   a complex expression like "COUNT(table.field) > 3"
935    * @param $args
936    *   An associative array of arguments.
937    *
938    * @see QueryConditionInterface::having()
939    */
940   public function addHavingExpression($group, $snippet, $args = []) {
941     // Ensure all variants of 0 are actually 0. Thus '', 0 and NULL are all
942     // the default group.
943     if (empty($group)) {
944       $group = 0;
945     }
946
947     // Check for a group.
948     if (!isset($this->having[$group])) {
949       $this->setWhereGroup('AND', $group, 'having');
950     }
951
952     // Add the clause and the args.
953     $this->having[$group]['conditions'][] = [
954       'field' => $snippet,
955       'value' => $args,
956       'operator' => 'formula',
957     ];
958   }
959
960   /**
961    * Add an ORDER BY clause to the query.
962    *
963    * @param $table
964    *   The table this field is part of. If a formula, enter NULL.
965    *   If you want to orderby random use "rand" as table and nothing else.
966    * @param $field
967    *   The field or formula to sort on. If already a field, enter NULL
968    *   and put in the alias.
969    * @param $order
970    *   Either ASC or DESC.
971    * @param $alias
972    *   The alias to add the field as. In SQL, all fields in the order by
973    *   must also be in the SELECT portion. If an $alias isn't specified
974    *   one will be generated for from the $field; however, if the
975    *   $field is a formula, this alias will likely fail.
976    * @param $params
977    *   Any params that should be passed through to the addField.
978    */
979   public function addOrderBy($table, $field = NULL, $order = 'ASC', $alias = '', $params = []) {
980     // Only ensure the table if it's not the special random key.
981     // @todo: Maybe it would make sense to just add an addOrderByRand or something similar.
982     if ($table && $table != 'rand') {
983       $this->ensureTable($table);
984     }
985
986     // Only fill out this aliasing if there is a table;
987     // otherwise we assume it is a formula.
988     if (!$alias && $table) {
989       $as = $table . '_' . $field;
990     }
991     else {
992       $as = $alias;
993     }
994
995     if ($field) {
996       $as = $this->addField($table, $field, $as, $params);
997     }
998
999     $this->orderby[] = [
1000       'field' => $as,
1001       'direction' => strtoupper($order)
1002     ];
1003   }
1004
1005   /**
1006    * Add a simple GROUP BY clause to the query. The caller is responsible
1007    * for ensuring that the fields are fully qualified and the table is properly
1008    * added.
1009    */
1010   public function addGroupBy($clause) {
1011     // Only add it if it's not already in there.
1012     if (!in_array($clause, $this->groupby)) {
1013       $this->groupby[] = $clause;
1014     }
1015   }
1016
1017   /**
1018    * Returns the alias for the given field added to $table.
1019    *
1020    * @access protected
1021    *
1022    * @see \Drupal\views\Plugin\views\query\Sql::addField
1023    */
1024   protected function getFieldAlias($table_alias, $field) {
1025     return isset($this->fieldAliases[$table_alias][$field]) ? $this->fieldAliases[$table_alias][$field] : FALSE;
1026   }
1027
1028   /**
1029    * Adds a query tag to the sql object.
1030    *
1031    * @see SelectQuery::addTag()
1032    */
1033   public function addTag($tag) {
1034     $this->tags[] = $tag;
1035   }
1036
1037   /**
1038    * Generates a unique placeholder used in the db query.
1039    */
1040   public function placeholder($base = 'views') {
1041     static $placeholders = [];
1042     if (!isset($placeholders[$base])) {
1043       $placeholders[$base] = 0;
1044       return ':' . $base;
1045     }
1046     else {
1047       return ':' . $base . ++$placeholders[$base];
1048     }
1049   }
1050
1051   /**
1052    * Construct the "WHERE" or "HAVING" part of the query.
1053    *
1054    * As views has to wrap the conditions from arguments with AND, a special
1055    * group is wrapped around all conditions. This special group has the ID 0.
1056    * There is other code in filters which makes sure that the group IDs are
1057    * higher than zero.
1058    *
1059    * @param $where
1060    *   'where' or 'having'.
1061    */
1062   protected function buildCondition($where = 'where') {
1063     $has_condition = FALSE;
1064     $has_arguments = FALSE;
1065     $has_filter = FALSE;
1066
1067     $main_group = new Condition('AND');
1068     $filter_group = $this->groupOperator == 'OR' ? new Condition('OR') : new Condition('AND');
1069
1070     foreach ($this->$where as $group => $info) {
1071
1072       if (!empty($info['conditions'])) {
1073         $sub_group = $info['type'] == 'OR' ? new Condition('OR') : new Condition('AND');
1074         foreach ($info['conditions'] as $clause) {
1075           if ($clause['operator'] == 'formula') {
1076             $has_condition = TRUE;
1077             $sub_group->where($clause['field'], $clause['value']);
1078           }
1079           else {
1080             $has_condition = TRUE;
1081             $sub_group->condition($clause['field'], $clause['value'], $clause['operator']);
1082           }
1083         }
1084
1085         // Add the item to the filter group.
1086         if ($group != 0) {
1087           $has_filter = TRUE;
1088           $filter_group->condition($sub_group);
1089         }
1090         else {
1091           $has_arguments = TRUE;
1092           $main_group->condition($sub_group);
1093         }
1094       }
1095     }
1096
1097     if ($has_filter) {
1098       $main_group->condition($filter_group);
1099     }
1100
1101     if (!$has_arguments && $has_condition) {
1102       return $filter_group;
1103     }
1104     if ($has_arguments && $has_condition) {
1105       return $main_group;
1106     }
1107   }
1108
1109   /**
1110    * Returns a list of non-aggregates to be added to the "group by" clause.
1111    *
1112    * Non-aggregates are fields that have no aggregation function (count, sum,
1113    * etc) applied. Since the SQL standard requires all fields to either have
1114    * an aggregation function applied, or to be in the GROUP BY clause, Views
1115    * gathers those fields and adds them to the GROUP BY clause.
1116    *
1117    * @return array
1118    *   An array of the fieldnames which are non-aggregates.
1119    */
1120   protected function getNonAggregates() {
1121     $non_aggregates = [];
1122     foreach ($this->fields as $field) {
1123       $string = '';
1124       if (!empty($field['table'])) {
1125         $string .= $field['table'] . '.';
1126       }
1127       $string .= $field['field'];
1128       $fieldname = (!empty($field['alias']) ? $field['alias'] : $string);
1129
1130       if (!empty($field['count'])) {
1131         // Retained for compatibility.
1132         $field['function'] = 'count';
1133       }
1134
1135       if (!empty($field['function'])) {
1136         $this->hasAggregate = TRUE;
1137       }
1138       // This is a formula, using no tables.
1139       elseif (empty($field['table'])) {
1140         $non_aggregates[] = $fieldname;
1141       }
1142       elseif (empty($field['aggregate'])) {
1143         $non_aggregates[] = $fieldname;
1144       }
1145
1146       if ($this->getCountOptimized) {
1147         // We only want the first field in this case.
1148         break;
1149       }
1150     }
1151
1152     return $non_aggregates;
1153   }
1154
1155   /**
1156    * Adds fields to the query.
1157    *
1158    * @param \Drupal\Core\Database\Query\SelectInterface $query
1159    *   The drupal query object.
1160    */
1161   protected function compileFields($query) {
1162     foreach ($this->fields as $field) {
1163       $string = '';
1164       if (!empty($field['table'])) {
1165         $string .= $field['table'] . '.';
1166       }
1167       $string .= $field['field'];
1168       $fieldname = (!empty($field['alias']) ? $field['alias'] : $string);
1169
1170       if (!empty($field['count'])) {
1171         // Retained for compatibility.
1172         $field['function'] = 'count';
1173       }
1174
1175       if (!empty($field['function'])) {
1176         $info = $this->getAggregationInfo();
1177         if (!empty($info[$field['function']]['method']) && is_callable([$this, $info[$field['function']]['method']])) {
1178           $string = $this::{$info[$field['function']]['method']}($field['function'], $string);
1179           $placeholders = !empty($field['placeholders']) ? $field['placeholders'] : [];
1180           $query->addExpression($string, $fieldname, $placeholders);
1181         }
1182
1183         $this->hasAggregate = TRUE;
1184       }
1185       // This is a formula, using no tables.
1186       elseif (empty($field['table'])) {
1187         $placeholders = !empty($field['placeholders']) ? $field['placeholders'] : [];
1188         $query->addExpression($string, $fieldname, $placeholders);
1189       }
1190       elseif ($this->distinct && !in_array($fieldname, $this->groupby)) {
1191         $query->addField(!empty($field['table']) ? $field['table'] : $this->view->storage->get('base_table'), $field['field'], $fieldname);
1192       }
1193       elseif (empty($field['aggregate'])) {
1194         $query->addField(!empty($field['table']) ? $field['table'] : $this->view->storage->get('base_table'), $field['field'], $fieldname);
1195       }
1196
1197       if ($this->getCountOptimized) {
1198         // We only want the first field in this case.
1199         break;
1200       }
1201     }
1202   }
1203
1204   /**
1205    * Generate a query and a countquery from all of the information supplied
1206    * to the object.
1207    *
1208    * @param $get_count
1209    *   Provide a countquery if this is true, otherwise provide a normal query.
1210    */
1211   public function query($get_count = FALSE) {
1212     // Check query distinct value.
1213     if (empty($this->noDistinct) && $this->distinct && !empty($this->fields)) {
1214       $base_field_alias = $this->addField($this->view->storage->get('base_table'), $this->view->storage->get('base_field'));
1215       $this->addGroupBy($base_field_alias);
1216       $distinct = TRUE;
1217     }
1218
1219     /**
1220      * An optimized count query includes just the base field instead of all the fields.
1221      * Determine of this query qualifies by checking for a groupby or distinct.
1222      */
1223     if ($get_count && !$this->groupby) {
1224       foreach ($this->fields as $field) {
1225         if (!empty($field['distinct']) || !empty($field['function'])) {
1226           $this->getCountOptimized = FALSE;
1227           break;
1228         }
1229       }
1230     }
1231     else {
1232       $this->getCountOptimized = FALSE;
1233     }
1234     if (!isset($this->getCountOptimized)) {
1235       $this->getCountOptimized = TRUE;
1236     }
1237
1238     $options = [];
1239     $target = 'default';
1240     $key = 'default';
1241     // Detect an external database and set the
1242     if (isset($this->view->base_database)) {
1243       $key = $this->view->base_database;
1244     }
1245
1246     // Set the replica target if the replica option is set
1247     if (!empty($this->options['replica'])) {
1248       $target = 'replica';
1249     }
1250
1251     // Go ahead and build the query.
1252     // db_select doesn't support to specify the key, so use getConnection directly.
1253     $query = Database::getConnection($target, $key)
1254       ->select($this->view->storage->get('base_table'), $this->view->storage->get('base_table'), $options)
1255       ->addTag('views')
1256       ->addTag('views_' . $this->view->storage->id());
1257
1258     // Add the tags added to the view itself.
1259     foreach ($this->tags as $tag) {
1260       $query->addTag($tag);
1261     }
1262
1263     if (!empty($distinct)) {
1264       $query->distinct();
1265     }
1266
1267     // Add all the tables to the query via joins. We assume all LEFT joins.
1268     foreach ($this->tableQueue as $table) {
1269       if (is_object($table['join'])) {
1270         $table['join']->buildJoin($query, $table, $this);
1271       }
1272     }
1273
1274     // Assemble the groupby clause, if any.
1275     $this->hasAggregate = FALSE;
1276     $non_aggregates = $this->getNonAggregates();
1277     if (count($this->having)) {
1278       $this->hasAggregate = TRUE;
1279     }
1280     elseif (!$this->hasAggregate) {
1281       // Allow 'GROUP BY' even no aggregation function has been set.
1282       $this->hasAggregate = $this->view->display_handler->getOption('group_by');
1283     }
1284     $groupby = [];
1285     if ($this->hasAggregate && (!empty($this->groupby) || !empty($non_aggregates))) {
1286       $groupby = array_unique(array_merge($this->groupby, $non_aggregates));
1287     }
1288
1289     // Make sure each entity table has the base field added so that the
1290     // entities can be loaded.
1291     $entity_information = $this->getEntityTableInfo();
1292     if ($entity_information) {
1293       $params = [];
1294       if ($groupby) {
1295         // Handle grouping, by retrieving the minimum entity_id.
1296         $params = [
1297           'function' => 'min',
1298         ];
1299       }
1300
1301       foreach ($entity_information as $entity_type_id => $info) {
1302         $entity_type = \Drupal::entityManager()->getDefinition($info['entity_type']);
1303         $base_field = !$info['revision'] ? $entity_type->getKey('id') : $entity_type->getKey('revision');
1304         $this->addField($info['alias'], $base_field, '', $params);
1305       }
1306     }
1307
1308     // Add all fields to the query.
1309     $this->compileFields($query);
1310
1311     // Add groupby.
1312     if ($groupby) {
1313       foreach ($groupby as $field) {
1314         // Handle group by of field without table alias to avoid ambiguous
1315         // column error.
1316         if ($field == $this->view->storage->get('base_field')) {
1317           $field = $this->view->storage->get('base_table') . '.' . $field;
1318         }
1319         $query->groupBy($field);
1320       }
1321       if (!empty($this->having) && $condition = $this->buildCondition('having')) {
1322         $query->havingCondition($condition);
1323       }
1324     }
1325
1326     if (!$this->getCountOptimized) {
1327       // we only add the orderby if we're not counting.
1328       if ($this->orderby) {
1329         foreach ($this->orderby as $order) {
1330           if ($order['field'] == 'rand_') {
1331             $query->orderRandom();
1332           }
1333           else {
1334             $query->orderBy($order['field'], $order['direction']);
1335           }
1336         }
1337       }
1338     }
1339
1340     if (!empty($this->where) && $condition = $this->buildCondition('where')) {
1341       $query->condition($condition);
1342     }
1343
1344     // Add a query comment.
1345     if (!empty($this->options['query_comment'])) {
1346       $query->comment($this->options['query_comment']);
1347     }
1348
1349     // Add the query tags.
1350     if (!empty($this->options['query_tags'])) {
1351       foreach ($this->options['query_tags'] as $tag) {
1352         $query->addTag($tag);
1353       }
1354     }
1355
1356     // Add all query substitutions as metadata.
1357     $query->addMetaData('views_substitutions', \Drupal::moduleHandler()->invokeAll('views_query_substitutions', [$this->view]));
1358
1359     return $query;
1360   }
1361
1362   /**
1363    * Get the arguments attached to the WHERE and HAVING clauses of this query.
1364    */
1365   public function getWhereArgs() {
1366     $args = [];
1367     foreach ($this->where as $where) {
1368       $args = array_merge($args, $where['args']);
1369     }
1370     foreach ($this->having as $having) {
1371       $args = array_merge($args, $having['args']);
1372     }
1373     return $args;
1374   }
1375
1376   /**
1377    * Let modules modify the query just prior to finalizing it.
1378    */
1379   public function alter(ViewExecutable $view) {
1380     \Drupal::moduleHandler()->invokeAll('views_query_alter', [$view, $this]);
1381   }
1382
1383   /**
1384    * Builds the necessary info to execute the query.
1385    */
1386   public function build(ViewExecutable $view) {
1387     // Make the query distinct if the option was set.
1388     if (!empty($this->options['distinct'])) {
1389       $this->setDistinct(TRUE);
1390     }
1391
1392     // Store the view in the object to be able to use it later.
1393     $this->view = $view;
1394
1395     $view->initPager();
1396
1397     // Let the pager modify the query to add limits.
1398     $view->pager->query();
1399
1400     $view->build_info['query'] = $this->query();
1401     $view->build_info['count_query'] = $this->query(TRUE);
1402   }
1403
1404   /**
1405    * Executes the query and fills the associated view object with according
1406    * values.
1407    *
1408    * Values to set: $view->result, $view->total_rows, $view->execute_time,
1409    * $view->current_page.
1410    */
1411   public function execute(ViewExecutable $view) {
1412     $query = $view->build_info['query'];
1413     $count_query = $view->build_info['count_query'];
1414
1415     $query->addMetaData('view', $view);
1416     $count_query->addMetaData('view', $view);
1417
1418     if (empty($this->options['disable_sql_rewrite'])) {
1419       $base_table_data = Views::viewsData()->get($this->view->storage->get('base_table'));
1420       if (isset($base_table_data['table']['base']['access query tag'])) {
1421         $access_tag = $base_table_data['table']['base']['access query tag'];
1422         $query->addTag($access_tag);
1423         $count_query->addTag($access_tag);
1424       }
1425
1426       if (isset($base_table_data['table']['base']['query metadata'])) {
1427         foreach ($base_table_data['table']['base']['query metadata'] as $key => $value) {
1428           $query->addMetaData($key, $value);
1429           $count_query->addMetaData($key, $value);
1430         }
1431       }
1432     }
1433
1434     if ($query) {
1435       $additional_arguments = \Drupal::moduleHandler()->invokeAll('views_query_substitutions', [$view]);
1436
1437       // Count queries must be run through the preExecute() method.
1438       // If not, then hook_query_node_access_alter() may munge the count by
1439       // adding a distinct against an empty query string
1440       // (e.g. COUNT DISTINCT(1) ...) and no pager will return.
1441       // See pager.inc > PagerDefault::execute()
1442       // http://api.drupal.org/api/drupal/includes--pager.inc/function/PagerDefault::execute/7
1443       // See https://www.drupal.org/node/1046170.
1444       $count_query->preExecute();
1445
1446       // Build the count query.
1447       $count_query = $count_query->countQuery();
1448
1449       // Add additional arguments as a fake condition.
1450       // XXX: this doesn't work, because PDO mandates that all bound arguments
1451       // are used on the query. TODO: Find a better way to do this.
1452       if (!empty($additional_arguments)) {
1453         // $query->where('1 = 1', $additional_arguments);
1454         // $count_query->where('1 = 1', $additional_arguments);
1455       }
1456
1457       $start = microtime(TRUE);
1458
1459       try {
1460         if ($view->pager->useCountQuery() || !empty($view->get_total_rows)) {
1461           $view->pager->executeCountQuery($count_query);
1462         }
1463
1464         // Let the pager modify the query to add limits.
1465         $view->pager->preExecute($query);
1466
1467         if (!empty($this->limit) || !empty($this->offset)) {
1468           // We can't have an offset without a limit, so provide a very large limit instead.
1469           $limit  = intval(!empty($this->limit) ? $this->limit : 999999);
1470           $offset = intval(!empty($this->offset) ? $this->offset : 0);
1471           $query->range($offset, $limit);
1472         }
1473
1474         $result = $query->execute();
1475         $result->setFetchMode(\PDO::FETCH_CLASS, 'Drupal\views\ResultRow');
1476
1477         // Setup the result row objects.
1478         $view->result = iterator_to_array($result);
1479         array_walk($view->result, function (ResultRow $row, $index) {
1480           $row->index = $index;
1481         });
1482
1483         $view->pager->postExecute($view->result);
1484         $view->pager->updatePageInfo();
1485         $view->total_rows = $view->pager->getTotalItems();
1486
1487         // Load all entities contained in the results.
1488         $this->loadEntities($view->result);
1489       }
1490       catch (DatabaseExceptionWrapper $e) {
1491         $view->result = [];
1492         if (!empty($view->live_preview)) {
1493           drupal_set_message($e->getMessage(), 'error');
1494         }
1495         else {
1496           throw new DatabaseExceptionWrapper("Exception in {$view->storage->label()}[{$view->storage->id()}]: {$e->getMessage()}");
1497         }
1498       }
1499
1500     }
1501     else {
1502       $start = microtime(TRUE);
1503     }
1504     $view->execute_time = microtime(TRUE) - $start;
1505   }
1506
1507   /**
1508    * Loads all entities contained in the passed-in $results.
1509    *.
1510    * If the entity belongs to the base table, then it gets stored in
1511    * $result->_entity. Otherwise, it gets stored in
1512    * $result->_relationship_entities[$relationship_id];
1513    *
1514    * @param \Drupal\views\ResultRow[] $results
1515    *   The result of the SQL query.
1516    */
1517   public function loadEntities(&$results) {
1518     $entity_information = $this->getEntityTableInfo();
1519     // No entity tables found, nothing else to do here.
1520     if (empty($entity_information)) {
1521       return;
1522     }
1523
1524     // Extract all entity types from entity_information.
1525     $entity_types = [];
1526     foreach ($entity_information as $info) {
1527       $entity_type = $info['entity_type'];
1528       if (!isset($entity_types[$entity_type])) {
1529         $entity_types[$entity_type] = $this->entityTypeManager->getDefinition($entity_type);
1530       }
1531     }
1532
1533     // Assemble a list of entities to load.
1534     $entity_ids_by_type = [];
1535     $revision_ids_by_type = [];
1536     foreach ($entity_information as $info) {
1537       $relationship_id = $info['relationship_id'];
1538       $entity_type = $info['entity_type'];
1539       /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_info */
1540       $entity_info = $entity_types[$entity_type];
1541       $revision = $info['revision'];
1542       $id_key = !$revision ? $entity_info->getKey('id') : $entity_info->getKey('revision');
1543       $id_alias = $this->getFieldAlias($info['alias'], $id_key);
1544
1545       foreach ($results as $index => $result) {
1546         // Store the entity id if it was found.
1547         if (isset($result->{$id_alias}) && $result->{$id_alias} != '') {
1548           if ($revision) {
1549             $revision_ids_by_type[$entity_type][$index][$relationship_id] = $result->$id_alias;
1550           }
1551           else {
1552             $entity_ids_by_type[$entity_type][$index][$relationship_id] = $result->$id_alias;
1553           }
1554         }
1555       }
1556     }
1557
1558     // Load all entities and assign them to the correct result row.
1559     foreach ($entity_ids_by_type as $entity_type => $ids) {
1560       $entity_storage = $this->entityTypeManager->getStorage($entity_type);
1561       $flat_ids = iterator_to_array(new \RecursiveIteratorIterator(new \RecursiveArrayIterator($ids)), FALSE);
1562
1563       $entities = $entity_storage->loadMultiple(array_unique($flat_ids));
1564       $results = $this->assignEntitiesToResult($ids, $entities, $results);
1565     }
1566
1567     // Now load all revisions.
1568     foreach ($revision_ids_by_type as $entity_type => $revision_ids) {
1569       $entity_storage = $this->entityTypeManager->getStorage($entity_type);
1570       $entities = [];
1571
1572       foreach ($revision_ids as $index => $revision_id_by_relationship) {
1573         foreach ($revision_id_by_relationship as $revision => $revision_id) {
1574           // Drupal core currently has no way to load multiple revisions.
1575           $entity = $entity_storage->loadRevision($revision_id);
1576           $entities[$revision_id] = $entity;
1577         }
1578       }
1579
1580       $results = $this->assignEntitiesToResult($revision_ids, $entities, $results);
1581     }
1582   }
1583
1584   /**
1585    * Sets entities onto the view result row objects.
1586    *
1587    * This method takes into account the relationship in which the entity was
1588    * needed in the first place.
1589    *
1590    * @param mixed[][] $ids
1591    *   A two dimensional array of identifiers (entity ID / revision ID) keyed by
1592    *   relationship.
1593    * @param \Drupal\Core\Entity\EntityInterface[] $entities
1594    *   An array of entities keyed by their identified (entity ID / revision ID).
1595    * @param \Drupal\views\ResultRow[] $results
1596    *   The entire views result.
1597    *
1598    * @return \Drupal\views\ResultRow[]
1599    *   The changed views results.
1600    */
1601   protected function assignEntitiesToResult($ids, array $entities, array $results) {
1602     foreach ($ids as $index => $relationships) {
1603       foreach ($relationships as $relationship_id => $id) {
1604         if (isset($entities[$id])) {
1605           $entity = $entities[$id];
1606         }
1607         else {
1608           $entity = NULL;
1609         }
1610
1611         if ($relationship_id == 'none') {
1612           $results[$index]->_entity = $entity;
1613         }
1614         else {
1615           $results[$index]->_relationship_entities[$relationship_id] = $entity;
1616         }
1617       }
1618     }
1619     return $results;
1620   }
1621
1622   /**
1623    * {@inheritdoc}
1624    */
1625   public function getCacheTags() {
1626     $tags = [];
1627     // Add cache tags for each row, if there is an entity associated with it.
1628     if (!$this->hasAggregate) {
1629       foreach ($this->getAllEntities() as $entity) {
1630         $tags = Cache::mergeTags($entity->getCacheTags(), $tags);
1631       }
1632     }
1633
1634     return $tags;
1635   }
1636
1637   /**
1638    * {@inheritdoc}
1639    */
1640   public function getCacheMaxAge() {
1641     $max_age = parent::getCacheMaxAge();
1642     foreach ($this->getAllEntities() as $entity) {
1643       $max_age = Cache::mergeMaxAges($max_age, $entity->getCacheMaxAge());
1644     }
1645
1646     return $max_age;
1647   }
1648
1649   /**
1650    * Gets all the involved entities of the view.
1651    *
1652    * @return \Drupal\Core\Entity\EntityInterface[]
1653    */
1654   protected function getAllEntities() {
1655     $entities = [];
1656     foreach ($this->view->result as $row) {
1657       if ($row->_entity) {
1658         $entities[] = $row->_entity;
1659       }
1660       foreach ($row->_relationship_entities as $entity) {
1661         $entities[] = $entity;
1662       }
1663     }
1664
1665     return $entities;
1666   }
1667
1668   public function addSignature(ViewExecutable $view) {
1669     $view->query->addField(NULL, "'" . $view->storage->id() . ':' . $view->current_display . "'", 'view_name');
1670   }
1671
1672   public function getAggregationInfo() {
1673     // @todo -- need a way to get database specific and customized aggregation
1674     // functions into here.
1675     return [
1676       'group' => [
1677         'title' => $this->t('Group results together'),
1678         'is aggregate' => FALSE,
1679       ],
1680       'count' => [
1681         'title' => $this->t('Count'),
1682         'method' => 'aggregationMethodSimple',
1683         'handler' => [
1684           'argument' => 'groupby_numeric',
1685           'field' => 'numeric',
1686           'filter' => 'groupby_numeric',
1687           'sort' => 'groupby_numeric',
1688         ],
1689       ],
1690       'count_distinct' => [
1691         'title' => $this->t('Count DISTINCT'),
1692         'method' => 'aggregationMethodDistinct',
1693         'handler' => [
1694           'argument' => 'groupby_numeric',
1695           'field' => 'numeric',
1696           'filter' => 'groupby_numeric',
1697           'sort' => 'groupby_numeric',
1698         ],
1699       ],
1700       'sum' => [
1701         'title' => $this->t('Sum'),
1702         'method' => 'aggregationMethodSimple',
1703         'handler' => [
1704           'argument' => 'groupby_numeric',
1705           'field' => 'numeric',
1706           'filter' => 'groupby_numeric',
1707           'sort' => 'groupby_numeric',
1708         ],
1709       ],
1710       'avg' => [
1711         'title' => $this->t('Average'),
1712         'method' => 'aggregationMethodSimple',
1713         'handler' => [
1714           'argument' => 'groupby_numeric',
1715           'field' => 'numeric',
1716           'filter' => 'groupby_numeric',
1717           'sort' => 'groupby_numeric',
1718         ],
1719       ],
1720       'min' => [
1721         'title' => $this->t('Minimum'),
1722         'method' => 'aggregationMethodSimple',
1723         'handler' => [
1724           'argument' => 'groupby_numeric',
1725           'field' => 'numeric',
1726           'filter' => 'groupby_numeric',
1727           'sort' => 'groupby_numeric',
1728         ],
1729       ],
1730       'max' => [
1731         'title' => $this->t('Maximum'),
1732         'method' => 'aggregationMethodSimple',
1733         'handler' => [
1734           'argument' => 'groupby_numeric',
1735           'field' => 'numeric',
1736           'filter' => 'groupby_numeric',
1737           'sort' => 'groupby_numeric',
1738         ],
1739       ],
1740       'stddev_pop' => [
1741         'title' => $this->t('Standard deviation'),
1742         'method' => 'aggregationMethodSimple',
1743         'handler' => [
1744           'argument' => 'groupby_numeric',
1745           'field' => 'numeric',
1746           'filter' => 'groupby_numeric',
1747           'sort' => 'groupby_numeric',
1748         ],
1749       ]
1750     ];
1751   }
1752
1753   public function aggregationMethodSimple($group_type, $field) {
1754     return strtoupper($group_type) . '(' . $field . ')';
1755   }
1756
1757   public function aggregationMethodDistinct($group_type, $field) {
1758     $group_type = str_replace('_distinct', '', $group_type);
1759     return strtoupper($group_type) . '(DISTINCT ' . $field . ')';
1760   }
1761
1762   /**
1763    * {@inheritdoc}
1764    */
1765   public function getDateField($field) {
1766     $db_type = Database::getConnection()->databaseType();
1767     $offset = $this->setupTimezone();
1768     if (isset($offset) && !is_numeric($offset)) {
1769       $dtz = new \DateTimeZone($offset);
1770       $dt = new \DateTime('now', $dtz);
1771       $offset_seconds = $dtz->getOffset($dt);
1772     }
1773
1774     switch ($db_type) {
1775       case 'mysql':
1776         $field = "DATE_ADD('19700101', INTERVAL $field SECOND)";
1777         if (!empty($offset)) {
1778           $field = "($field + INTERVAL $offset_seconds SECOND)";
1779         }
1780         break;
1781       case 'pgsql':
1782         $field = "TO_TIMESTAMP($field)";
1783         if (!empty($offset)) {
1784           $field = "($field + INTERVAL '$offset_seconds SECONDS')";
1785         }
1786         break;
1787       case 'sqlite':
1788         if (!empty($offset)) {
1789           $field = "($field + $offset_seconds)";
1790         }
1791         break;
1792     }
1793
1794     return $field;
1795   }
1796
1797   /**
1798    * {@inheritdoc}
1799    */
1800   public function setupTimezone() {
1801     $timezone = drupal_get_user_timezone();
1802
1803     // set up the database timezone
1804     $db_type = Database::getConnection()->databaseType();
1805     if (in_array($db_type, ['mysql', 'pgsql'])) {
1806       $offset = '+00:00';
1807       static $already_set = FALSE;
1808       if (!$already_set) {
1809         if ($db_type == 'pgsql') {
1810           Database::getConnection()->query("SET TIME ZONE INTERVAL '$offset' HOUR TO MINUTE");
1811         }
1812         elseif ($db_type == 'mysql') {
1813           Database::getConnection()->query("SET @@session.time_zone = '$offset'");
1814         }
1815
1816         $already_set = TRUE;
1817       }
1818     }
1819
1820     return $timezone;
1821   }
1822
1823   /**
1824    * {@inheritdoc}
1825    */
1826   public function getDateFormat($field, $format, $string_date = FALSE) {
1827     $db_type = Database::getConnection()->databaseType();
1828     switch ($db_type) {
1829       case 'mysql':
1830         $replace = [
1831           'Y' => '%Y',
1832           'y' => '%y',
1833           'M' => '%b',
1834           'm' => '%m',
1835           'n' => '%c',
1836           'F' => '%M',
1837           'D' => '%a',
1838           'd' => '%d',
1839           'l' => '%W',
1840           'j' => '%e',
1841           'W' => '%v',
1842           'H' => '%H',
1843           'h' => '%h',
1844           'i' => '%i',
1845           's' => '%s',
1846           'A' => '%p',
1847         ];
1848         $format = strtr($format, $replace);
1849         return "DATE_FORMAT($field, '$format')";
1850       case 'pgsql':
1851         $replace = [
1852           'Y' => 'YYYY',
1853           'y' => 'YY',
1854           'M' => 'Mon',
1855           'm' => 'MM',
1856           // No format for Numeric representation of a month, without leading
1857           // zeros.
1858           'n' => 'MM',
1859           'F' => 'Month',
1860           'D' => 'Dy',
1861           'd' => 'DD',
1862           'l' => 'Day',
1863           // No format for Day of the month without leading zeros.
1864           'j' => 'DD',
1865           'W' => 'IW',
1866           'H' => 'HH24',
1867           'h' => 'HH12',
1868           'i' => 'MI',
1869           's' => 'SS',
1870           'A' => 'AM',
1871         ];
1872         $format = strtr($format, $replace);
1873         if (!$string_date) {
1874           return "TO_CHAR($field, '$format')";
1875         }
1876         // In order to allow for partials (eg, only the year), transform to a
1877         // date, back to a string again.
1878         return "TO_CHAR(TO_TIMESTAMP($field, 'YYYY-MM-DD HH24:MI:SS'), '$format')";
1879       case 'sqlite':
1880         $replace = [
1881           'Y' => '%Y',
1882           // No format for 2 digit year number.
1883           'y' => '%Y',
1884           // No format for 3 letter month name.
1885           'M' => '%m',
1886           'm' => '%m',
1887           // No format for month number without leading zeros.
1888           'n' => '%m',
1889           // No format for full month name.
1890           'F' => '%m',
1891           // No format for 3 letter day name.
1892           'D' => '%d',
1893           'd' => '%d',
1894           // No format for full day name.
1895           'l' => '%d',
1896           // no format for day of month number without leading zeros.
1897           'j' => '%d',
1898           'W' => '%W',
1899           'H' => '%H',
1900           // No format for 12 hour hour with leading zeros.
1901           'h' => '%H',
1902           'i' => '%M',
1903           's' => '%S',
1904           // No format for AM/PM.
1905           'A' => '',
1906         ];
1907         $format = strtr($format, $replace);
1908
1909         // Don't use the 'unixepoch' flag for string date comparisons.
1910         $unixepoch = $string_date ? '' : ", 'unixepoch'";
1911
1912         // SQLite does not have a ISO week substitution string, so it needs
1913         // special handling.
1914         // @see http://wikipedia.org/wiki/ISO_week_date#Calculation
1915         // @see http://stackoverflow.com/a/15511864/1499564
1916         if ($format === '%W') {
1917           $expression = "((strftime('%j', date(strftime('%Y-%m-%d', $field" . $unixepoch . "), '-3 days', 'weekday 4')) - 1) / 7 + 1)";
1918         }
1919         else {
1920           $expression = "strftime('$format', $field" . $unixepoch . ")";
1921         }
1922         // The expression yields a string, but the comparison value is an
1923         // integer in case the comparison value is a float, integer, or numeric.
1924         // All of the above SQLite format tokens only produce integers. However,
1925         // the given $format may contain 'Y-m-d', which results in a string.
1926         // @see \Drupal\Core\Database\Driver\sqlite\Connection::expandArguments()
1927         // @see http://www.sqlite.org/lang_datefunc.html
1928         // @see http://www.sqlite.org/lang_expr.html#castexpr
1929         if (preg_match('/^(?:%\w)+$/', $format)) {
1930           $expression = "CAST($expression AS NUMERIC)";
1931         }
1932         return $expression;
1933     }
1934   }
1935
1936 }