[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/vendor/joomla/database/src/Sqlsrv/ -> SqlsrvQuery.php (source)

   1  <?php
   2  /**
   3   * Part of the Joomla Framework Database Package
   4   *
   5   * @copyright  Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved.
   6   * @license    GNU General Public License version 2 or later; see LICENSE
   7   */
   8  
   9  namespace Joomla\Database\Sqlsrv;
  10  
  11  use Joomla\Database\DatabaseInterface;
  12  use Joomla\Database\DatabaseQuery;
  13  use Joomla\Database\Query\QueryElement;
  14  
  15  /**
  16   * SQL Server Query Building Class.
  17   *
  18   * @since  1.0
  19   */
  20  class SqlsrvQuery extends DatabaseQuery
  21  {
  22      /**
  23       * The list of zero or null representation of a datetime.
  24       *
  25       * @var    array
  26       * @since  2.0.0
  27       */
  28      protected $nullDatetimeList = ['1900-01-01 00:00:00'];
  29  
  30      /**
  31       * Magic function to convert the query to a string.
  32       *
  33       * @return  string  The completed query.
  34       *
  35       * @since   1.0
  36       */
  37  	public function __toString()
  38      {
  39          // For the moment if we are given a query string we can't effectively process limits, fix this later
  40          if ($this->sql)
  41          {
  42              return $this->sql;
  43          }
  44  
  45          $query = '';
  46  
  47          switch ($this->type)
  48          {
  49              case 'select':
  50                  // Add required aliases for offset or fixGroupColumns method
  51                  $columns = $this->fixSelectAliases();
  52  
  53                  $query = (string) $this->select;
  54  
  55                  if ($this->group)
  56                  {
  57                      $this->fixGroupColumns($columns);
  58                  }
  59  
  60                  $query .= (string) $this->from;
  61  
  62                  if ($this->join)
  63                  {
  64                      // Special case for joins
  65                      foreach ($this->join as $join)
  66                      {
  67                          $query .= (string) $join;
  68                      }
  69                  }
  70  
  71                  if ($this->where)
  72                  {
  73                      $query .= (string) $this->where;
  74                  }
  75  
  76                  if ($this->selectRowNumber === null)
  77                  {
  78                      if ($this->group)
  79                      {
  80                          $query .= (string) $this->group;
  81                      }
  82  
  83                      if ($this->having)
  84                      {
  85                          $query .= (string) $this->having;
  86                      }
  87  
  88                      if ($this->merge)
  89                      {
  90                          // Special case for merge
  91                          foreach ($this->merge as $idx => $element)
  92                          {
  93                              $query .= (string) $element . ' AS merge_' . (int) ($idx + 1);
  94                          }
  95                      }
  96                  }
  97  
  98                  if ($this->order)
  99                  {
 100                      $query .= (string) $this->order;
 101                  }
 102                  else
 103                  {
 104                      $query .= PHP_EOL . '/*ORDER BY (SELECT 0)*/';
 105                  }
 106  
 107                  $query = $this->processLimit($query, $this->limit, $this->offset);
 108  
 109                  if ($this->alias !== null)
 110                  {
 111                      $query = '(' . $query . ') AS ' . $this->alias;
 112                  }
 113  
 114                  break;
 115  
 116              case 'querySet':
 117                  $query = $this->querySet;
 118  
 119                  if ($query->order || $query->limit || $query->offset)
 120                  {
 121                      // If ORDER BY or LIMIT statement exist then parentheses is required for the first query
 122                      $query = PHP_EOL . "SELECT * FROM ($query) AS merge_0";
 123                  }
 124  
 125                  if ($this->merge)
 126                  {
 127                      // Special case for merge
 128                      foreach ($this->merge as $idx => $element)
 129                      {
 130                          $query .= (string) $element . ' AS merge_' . (int) ($idx + 1);
 131                      }
 132                  }
 133  
 134                  if ($this->order)
 135                  {
 136                      $query .= (string) $this->order;
 137                  }
 138  
 139                  $query = $this->processLimit($query, $this->limit, $this->offset);
 140  
 141                  break;
 142  
 143              case 'insert':
 144                  $query .= (string) $this->insert;
 145  
 146                  // Set method
 147                  if ($this->set)
 148                  {
 149                      $query .= (string) $this->set;
 150                  }
 151                  elseif ($this->values)
 152                  {
 153                      // Columns-Values method
 154                      if ($this->columns)
 155                      {
 156                          $query .= (string) $this->columns;
 157                      }
 158  
 159                      $elements  = $this->insert->getElements();
 160                      $tableName = array_shift($elements);
 161  
 162                      $query .= 'VALUES ';
 163                      $query .= (string) $this->values;
 164  
 165                      if ($this->autoIncrementField)
 166                      {
 167                          $query = 'SET IDENTITY_INSERT ' . $tableName . ' ON;' . $query . 'SET IDENTITY_INSERT ' . $tableName . ' OFF;';
 168                      }
 169  
 170                      if ($this->where)
 171                      {
 172                          $query .= (string) $this->where;
 173                      }
 174                  }
 175  
 176                  break;
 177  
 178              case 'delete':
 179                  $query .= (string) $this->delete;
 180                  $query .= (string) $this->from;
 181  
 182                  if ($this->join)
 183                  {
 184                      // Special case for joins
 185                      foreach ($this->join as $join)
 186                      {
 187                          $query .= (string) $join;
 188                      }
 189                  }
 190  
 191                  if ($this->where)
 192                  {
 193                      $query .= (string) $this->where;
 194                  }
 195  
 196                  if ($this->order)
 197                  {
 198                      $query .= (string) $this->order;
 199                  }
 200  
 201                  break;
 202  
 203              case 'update':
 204                  if ($this->join)
 205                  {
 206                      $tmpUpdate    = $this->update;
 207                      $tmpFrom      = $this->from;
 208                      $this->update = null;
 209                      $this->from   = null;
 210  
 211                      $updateElem  = $tmpUpdate->getElements();
 212                      $updateArray = explode(' ', $updateElem[0]);
 213  
 214                      // Use table alias if exists
 215                      $this->update(end($updateArray));
 216                      $this->from($updateElem[0]);
 217  
 218                      $query .= (string) $this->update;
 219                      $query .= (string) $this->set;
 220                      $query .= (string) $this->from;
 221  
 222                      $this->update = $tmpUpdate;
 223                      $this->from   = $tmpFrom;
 224  
 225                      // Special case for joins
 226                      foreach ($this->join as $join)
 227                      {
 228                          $query .= (string) $join;
 229                      }
 230                  }
 231                  else
 232                  {
 233                      $query .= (string) $this->update;
 234                      $query .= (string) $this->set;
 235                  }
 236  
 237                  if ($this->where)
 238                  {
 239                      $query .= (string) $this->where;
 240                  }
 241  
 242                  if ($this->order)
 243                  {
 244                      $query .= (string) $this->order;
 245                  }
 246  
 247                  break;
 248  
 249              default:
 250                  $query = parent::__toString();
 251  
 252                  break;
 253          }
 254  
 255          return $query;
 256      }
 257  
 258      /**
 259       * Casts a value to a char.
 260       *
 261       * Ensure that the value is properly quoted before passing to the method.
 262       *
 263       * Usage:
 264       * $query->select($query->castAs('CHAR', 'a'));
 265       *
 266       * @param   string  $type    The type of string to cast as.
 267       * @param   string  $value   The value to cast as a char.
 268       * @param   string  $length  The value to cast as a char.
 269       *
 270       * @return  string  SQL statement to cast the value as a char type.
 271       *
 272       * @since   1.0
 273       */
 274  	public function castAs(string $type, string $value, ?string $length = null)
 275      {
 276          switch (strtoupper($type))
 277          {
 278              case 'CHAR':
 279                  if (!$length)
 280                  {
 281                      $length = '10';
 282                  }
 283  
 284                  return 'CAST(' . $value . ' as NVARCHAR(' . $length . '))';
 285  
 286              case 'INT':
 287                  return 'CAST(' . $value . ' AS INT)';
 288          }
 289  
 290          return parent::castAs($type, $value, $length);
 291      }
 292  
 293      /**
 294       * Gets the function to determine the length of a character string.
 295       *
 296       * @param   string       $field      A value.
 297       * @param   string|null  $operator   Comparison operator between charLength integer value and $condition
 298       * @param   string|null  $condition  Integer value to compare charLength with.
 299       *
 300       * @return  string  The required char length call.
 301       *
 302       * @since   1.0
 303       */
 304  	public function charLength($field, $operator = null, $condition = null)
 305      {
 306          $statement = 'DATALENGTH(' . $field . ')';
 307  
 308          if ($operator !== null && $condition !== null)
 309          {
 310              $statement .= ' ' . $operator . ' ' . $condition;
 311          }
 312  
 313          return $statement;
 314      }
 315  
 316      /**
 317       * Concatenates an array of column names or values.
 318       *
 319       * @param   string[]     $values     An array of values to concatenate.
 320       * @param   string|null  $separator  As separator to place between each value.
 321       *
 322       * @return  string  The concatenated values.
 323       *
 324       * @since   1.0
 325       */
 326  	public function concatenate($values, $separator = null)
 327      {
 328          if ($separator !== null)
 329          {
 330              return '(' . implode('+' . $this->quote($separator) . '+', $values) . ')';
 331          }
 332  
 333          return '(' . implode('+', $values) . ')';
 334      }
 335  
 336      /**
 337       * Gets the current date and time.
 338       *
 339       * @return  string
 340       *
 341       * @since   1.0
 342       */
 343  	public function currentTimestamp()
 344      {
 345          return 'GETDATE()';
 346      }
 347  
 348      /**
 349       * Get the length of a string in bytes.
 350       *
 351       * @param   string  $value  The string to measure.
 352       *
 353       * @return  integer
 354       *
 355       * @since   1.0
 356       */
 357  	public function length($value)
 358      {
 359          return 'LEN(' . $value . ')';
 360      }
 361  
 362      /**
 363       * Add a grouping column to the GROUP clause of the query.
 364       *
 365       * Usage:
 366       * $query->group('id');
 367       *
 368       * @param   mixed  $columns  A string or array of ordering columns.
 369       *
 370       * @return  SqlsrvQuery  Returns this object to allow chaining.
 371       *
 372       * @since   1.5.0
 373       */
 374  	public function group($columns)
 375      {
 376          if (!($this->db instanceof DatabaseInterface))
 377          {
 378              throw new \RuntimeException('JLIB_DATABASE_ERROR_INVALID_DB_OBJECT');
 379          }
 380  
 381          // Transform $columns into an array for filtering purposes
 382          \is_string($columns) && $columns = explode(',', str_replace(' ', '', $columns));
 383  
 384          // Get the _formatted_ FROM string and remove everything except `table AS alias`
 385          $fromStr = str_replace(['[', ']'], '', str_replace('#__', $this->db->getPrefix(), str_replace('FROM ', '', (string) $this->from)));
 386  
 387          // Start setting up an array of alias => table
 388          list($table, $alias) = preg_split("/\sAS\s/i", $fromStr);
 389  
 390          $tmpCols = $this->db->getTableColumns(trim($table));
 391          $cols    = [];
 392  
 393          foreach ($tmpCols as $name => $type)
 394          {
 395              $cols[] = $alias . '.' . $name;
 396          }
 397  
 398          // Now we need to get all tables from any joins
 399          // Go through all joins and add them to the tables array
 400          foreach ($this->join as $join)
 401          {
 402              $joinTbl = str_replace(
 403                  '#__',
 404                  $this->db->getPrefix(),
 405                  str_replace(
 406                      ']',
 407                      '',
 408                      preg_replace("/.*(#.+\sAS\s[^\s]*).*/i", '$1', (string) $join)
 409                  )
 410              );
 411  
 412              list($table, $alias) = preg_split("/\sAS\s/i", $joinTbl);
 413  
 414              $tmpCols = $this->db->getTableColumns(trim($table));
 415  
 416              foreach ($tmpCols as $name => $tmpColType)
 417              {
 418                  $cols[] = $alias . '.' . $name;
 419              }
 420          }
 421  
 422          $selectStr = str_replace('SELECT ', '', (string) $this->select);
 423  
 424          // Remove any functions (e.g. COUNT(), SUM(), CONCAT())
 425          $selectCols = preg_replace("/([^,]*\([^\)]*\)[^,]*,?)/", '', $selectStr);
 426  
 427          // Remove any "as alias" statements
 428          $selectCols = preg_replace("/(\sas\s[^,]*)/i", '', $selectCols);
 429  
 430          // Remove any extra commas
 431          $selectCols = preg_replace('/,{2,}/', ',', $selectCols);
 432  
 433          // Remove any trailing commas and all whitespaces
 434          $selectCols = trim(str_replace(' ', '', preg_replace('/,?$/', '', $selectCols)));
 435  
 436          // Get an array to compare against
 437          $selectCols = explode(',', $selectCols);
 438  
 439          // Find all alias.* and fill with proper table column names
 440          foreach ($selectCols as $key => $aliasColName)
 441          {
 442              if (preg_match("/.+\*/", $aliasColName, $match))
 443              {
 444                  // Grab the table alias minus the .*
 445                  $aliasStar = preg_replace("/(.+)\.\*/", '$1', $aliasColName);
 446  
 447                  // Unset the array key
 448                  unset($selectCols[$key]);
 449  
 450                  // Get the table name
 451                  $tableColumns = preg_grep("/{$aliasStar}\.+/", $cols);
 452                  $columns      = array_merge($columns, $tableColumns);
 453              }
 454          }
 455  
 456          // Finally, get a unique string of all column names that need to be included in the group statement
 457          $columns = array_unique(array_merge($columns, $selectCols));
 458          $columns = implode(',', $columns);
 459  
 460          // Recreate it every time, to ensure we have checked _all_ select statements
 461          $this->group = new QueryElement('GROUP BY', $columns);
 462  
 463          return $this;
 464      }
 465  
 466      /**
 467       * Aggregate function to get input values concatenated into a string, separated by delimiter
 468       *
 469       * Usage:
 470       * $query->groupConcat('id', ',');
 471       *
 472       * @param   string  $expression  The expression to apply concatenation to, this may be a column name or complex SQL statement.
 473       * @param   string  $separator   The delimiter of each concatenated value
 474       *
 475       * @return  string  Input values concatenated into a string, separated by delimiter
 476       *
 477       * @since   2.0.0
 478       */
 479  	public function groupConcat($expression, $separator = ',')
 480      {
 481          return 'string_agg(' . $expression . ', ' . $this->quote($separator) . ')';
 482      }
 483  
 484      /**
 485       * Get the function to return a random floating-point value
 486       *
 487       * Usage:
 488       * $query->rand();
 489       *
 490       * @return  string
 491       *
 492       * @since   1.5.0
 493       */
 494  	public function rand()
 495      {
 496          return ' NEWID() ';
 497      }
 498  
 499      /**
 500       * Find a value in a varchar used like a set.
 501       *
 502       * Ensure that the value is an integer before passing to the method.
 503       *
 504       * Usage:
 505       * $query->findInSet((int) $parent->id, 'a.assigned_cat_ids')
 506       *
 507       * @param   string  $value  The value to search for.
 508       * @param   string  $set    The set of values.
 509       *
 510       * @return  string  A representation of the MySQL find_in_set() function for the driver.
 511       *
 512       * @since   1.5.0
 513       */
 514  	public function findInSet($value, $set)
 515      {
 516          return "CHARINDEX(',$value,', ',' + $set + ',') > 0";
 517      }
 518  
 519      /**
 520       * Add required aliases to columns for select statement in subquery.
 521       *
 522       * @return  array[]  Array of columns with added missing aliases.
 523       *
 524       * @since  2.0.0
 525       */
 526  	protected function fixSelectAliases()
 527      {
 528          $operators = [
 529              '+' => '',
 530              '-' => '',
 531              '*' => '',
 532              '/' => '',
 533              '%' => '',
 534              '&' => '',
 535              '|' => '',
 536              '~' => '',
 537              '^' => '',
 538          ];
 539  
 540          // Split into array and remove comments
 541          $columns = $this->splitSqlExpression(implode(',', $this->select->getElements()));
 542  
 543          foreach ($columns as $i => $column)
 544          {
 545              $size = \count($column);
 546  
 547              if ($size == 0)
 548              {
 549                  continue;
 550              }
 551  
 552              if ($size > 2 && strcasecmp($column[$size - 2], 'AS') === 0)
 553              {
 554                  // Alias exists, replace it to uppercase
 555                  $columns[$i][$size - 2] = 'AS';
 556  
 557                  continue;
 558              }
 559  
 560              if ($i == 0 && stripos(' DISTINCT ALL ', " $column[0] ") !== false)
 561              {
 562                  // This words are reserved, they are not column names
 563                  array_shift($column);
 564                  $size--;
 565              }
 566  
 567              $lastWord = strtoupper($column[$size - 1]);
 568              $length   = \strlen($lastWord);
 569              $lastChar = $lastWord[$length - 1];
 570  
 571              if ($lastChar == '*')
 572              {
 573                  // Skip on wildcard
 574                  continue;
 575              }
 576  
 577              if ($lastChar == ')'
 578                  || ($size == 1 && $lastChar == "'")
 579                  || $lastWord[0] == '@'
 580                  || $lastWord == 'NULL'
 581                  || $lastWord == 'END'
 582                  || is_numeric($lastWord))
 583              {
 584                  /*
 585                   * Ends with:
 586                   * - SQL function
 587                   * - single static value like 'only '+'string'
 588                   * - @@var
 589                   * - NULL
 590                   * - CASE ... END
 591                   * - Numeric
 592                   */
 593                  $columns[$i][] = 'AS';
 594                  $columns[$i][] = $this->quoteName('columnAlias' . $i);
 595  
 596                  continue;
 597              }
 598  
 599              if ($size == 1)
 600              {
 601                  continue;
 602              }
 603  
 604              $lastChar2 = substr($column[$size - 2], -1);
 605  
 606              // Check if column ends with  '- a.x' or '- a. x'
 607              if (isset($operators[$lastChar2]) || ($size > 2 && $lastChar2 === '.' && isset($operators[substr($column[$size - 3], -1)])))
 608              {
 609                  // Ignore plus signs if column start with them
 610                  if ($size != 2 || ltrim($column[0], '+') !== '' || $column[1][0] === "'")
 611                  {
 612                      // If operator exists before last word then alias is required for subquery
 613                      $columns[$i][] = 'AS';
 614                      $columns[$i][] = $this->quoteName('columnAlias' . $i);
 615  
 616                      continue;
 617                  }
 618              }
 619              elseif ($column[$size - 1][0] !== '.' && $lastChar2 !== '.')
 620              {
 621                  // If columns is like name name2 then second word is alias.
 622                  // Add missing AS before the alias, exception for 'a. x' and 'a .x'
 623                  array_splice($columns[$i], -1, 0, 'AS');
 624              }
 625          }
 626  
 627          $selectColumns = [];
 628  
 629          foreach ($columns as $i => $column)
 630          {
 631              $selectColumns[$i] = implode(' ', $column);
 632          }
 633  
 634          $this->select = new QueryElement('SELECT', $selectColumns);
 635  
 636          return $columns;
 637      }
 638  
 639      /**
 640       * Add missing columns names to GROUP BY clause.
 641       *
 642       * @param   array[]  $selectColumns  Array of columns from splitSqlExpression method.
 643       *
 644       * @return  $this
 645       *
 646       * @since   2.0.0
 647       */
 648  	protected function fixGroupColumns($selectColumns)
 649      {
 650          // Cache tables columns
 651          static $cacheCols = [];
 652  
 653          // Known columns of all included tables
 654          $knownColumnsByAlias = [];
 655  
 656          $iquotes = ['"' => '', '[' => '', "'" => ''];
 657          $nquotes = ['"', '[', ']'];
 658  
 659          // Aggregate functions
 660          $aFuncs = [
 661              'AVG(',
 662              'CHECKSUM_AGG(',
 663              'COUNT(',
 664              'COUNT_BIG(',
 665              'GROUPING(',
 666              'GROUPING_ID(',
 667              'MIN(',
 668              'MAX(',
 669              'SUM(',
 670              'STDEV(',
 671              'STDEVP(',
 672              'VAR(',
 673              'VARP(',
 674          ];
 675  
 676          // Aggregated columns
 677          $filteredColumns = [];
 678  
 679          // Aliases found in SELECT statement
 680          $knownAliases   = [];
 681          $wildcardTables = [];
 682  
 683          foreach ($selectColumns as $i => $column)
 684          {
 685              $size = \count($column);
 686  
 687              if ($size === 0)
 688              {
 689                  continue;
 690              }
 691  
 692              if ($i == 0 && stripos(' DISTINCT ALL ', " $column[0] ") !== false)
 693              {
 694                  // These words are reserved, they are not column names
 695                  array_shift($selectColumns[0]);
 696                  array_shift($column);
 697                  $size--;
 698              }
 699  
 700              if ($size > 2 && $column[$size - 2] === 'AS')
 701              {
 702                  // Save and remove AS alias
 703                  $alias = $column[$size - 1];
 704  
 705                  if (isset($iquotes[$alias[0]]))
 706                  {
 707                      $alias = substr($alias, 1, -1);
 708                  }
 709  
 710                  // Remove alias
 711                  $selectColumns[$i] = $column = \array_slice($column, 0, -2);
 712  
 713                  if ($size === 3 || ($size === 4 && strpos('+-*/%&|~^', $column[0][0]) !== false))
 714                  {
 715                      $lastWord = $column[$size - 3];
 716  
 717                      if ($lastWord[0] === "'" || $lastWord === 'NULL' || is_numeric($lastWord))
 718                      {
 719                          unset($selectColumns[$i]);
 720  
 721                          continue;
 722                      }
 723                  }
 724  
 725                  // Remember pair alias => column expression
 726                  $knownAliases[$alias] = implode(' ', $column);
 727              }
 728  
 729              $aggregated = false;
 730  
 731              foreach ($column as $j => $block)
 732              {
 733                  if (substr($block, -2) === '.*')
 734                  {
 735                      // Found column ends with .*
 736                      if (isset($iquotes[$block[0]]))
 737                      {
 738                          // Quoted table
 739                          $wildcardTables[] = substr($block, 1, -3);
 740                      }
 741                      else
 742                      {
 743                          $wildcardTables[] = substr($block, 0, -2);
 744                      }
 745                  }
 746                  elseif (str_ireplace($aFuncs, '', $block) != $block)
 747                  {
 748                      $aggregated = true;
 749                  }
 750  
 751                  if ($block[0] === "'")
 752                  {
 753                      // Shrink static strings which could contain column name
 754                      $column[$j] = "''";
 755                  }
 756              }
 757  
 758              if (!$aggregated)
 759              {
 760                  // Without aggregated columns and aliases
 761                  $filteredColumns[] = implode(' ', $selectColumns[$i]);
 762              }
 763  
 764              // Without aliases and static strings
 765              $selectColumns[$i] = implode(' ', $column);
 766          }
 767  
 768          // If select statement use table.* expression
 769          if ($wildcardTables)
 770          {
 771              // Split FROM statement into list of tables
 772              $tables = $this->splitSqlExpression(implode(',', $this->from->getElements()));
 773  
 774              foreach ($tables as $i => $table)
 775              {
 776                  $table = implode(' ', $table);
 777  
 778                  // Exclude subquery from the FROM clause
 779                  if (strpos($table, '(') === false)
 780                  {
 781                      // Unquote
 782                      $table = str_replace($nquotes, '', $table);
 783                      $table = str_replace('#__', $this->db->getPrefix(), $table);
 784                      $table = explode(' ', $table);
 785                      $alias = end($table);
 786                      $table = $table[0];
 787  
 788                      // Chek if exists a wildcard with current alias table?
 789                      if (\in_array($alias, $wildcardTables, true))
 790                      {
 791                          if (!isset($cacheCols[$table]))
 792                          {
 793                              $cacheCols[$table] = $this->db->getTableColumns($table);
 794                          }
 795  
 796                          if ($this->join || $table != $alias)
 797                          {
 798                              foreach ($cacheCols[$table] as $name => $type)
 799                              {
 800                                  $knownColumnsByAlias[$alias][] = $alias . '.' . $name;
 801                              }
 802                          }
 803                          else
 804                          {
 805                              foreach ($cacheCols[$table] as $name => $type)
 806                              {
 807                                  $knownColumnsByAlias[$alias][] = $name;
 808                              }
 809                          }
 810                      }
 811                  }
 812              }
 813  
 814              // Now we need to get all tables from any joins
 815              // Go through all joins and add them to the tables array
 816              if ($this->join)
 817              {
 818                  foreach ($this->join as $join)
 819                  {
 820                      // Unquote and replace prefix
 821                      $joinTbl = str_replace($nquotes, '', (string) $join);
 822                      $joinTbl = str_replace('#__', $this->db->getPrefix(), $joinTbl);
 823  
 824                      // Exclude subquery
 825                      if (preg_match('/JOIN\s+(\w+)(?:\s+AS)?(?:\s+(\w+))?/i', $joinTbl, $matches))
 826                      {
 827                          $table = $matches[1];
 828                          $alias = $matches[2] ?? $table;
 829  
 830                          // Chek if exists a wildcard with current alias table?
 831                          if (\in_array($alias, $wildcardTables, true))
 832                          {
 833                              if (!isset($cacheCols[$table]))
 834                              {
 835                                  $cacheCols[$table] = $this->db->getTableColumns($table);
 836                              }
 837  
 838                              foreach ($cacheCols[$table] as $name => $type)
 839                              {
 840                                  $knownColumnsByAlias[$alias][] = $alias . '.' . $name;
 841                              }
 842                          }
 843                      }
 844                  }
 845              }
 846          }
 847  
 848          $selectExpression = implode(',', $selectColumns);
 849  
 850          // Split into the right columns
 851          $groupColumns = $this->splitSqlExpression(implode(',', $this->group->getElements()));
 852  
 853          // Remove column aliases from GROUP statement - SQLSRV does not support it
 854          foreach ($groupColumns as $i => $column)
 855          {
 856              $groupColumns[$i] = implode(' ', $column);
 857              $column           = str_replace($nquotes, '', $groupColumns[$i]);
 858  
 859              if (isset($knownAliases[$column]))
 860              {
 861                  // Be sure that this is not a valid column name
 862                  if (!preg_match('/\b' . preg_quote($column, '/') . '\b/', $selectExpression))
 863                  {
 864                      // Replace column alias by column expression
 865                      $groupColumns[$i] = $knownAliases[$column];
 866                  }
 867              }
 868          }
 869  
 870          // Find all alias.* and fill with proper table column names
 871          foreach ($filteredColumns as $i => $column)
 872          {
 873              if (substr($column, -2) === '.*')
 874              {
 875                  unset($filteredColumns[$i]);
 876  
 877                  // Extract alias.* columns into GROUP BY statement
 878                  $groupColumns = array_merge($groupColumns, $knownColumnsByAlias[substr($column, 0, -2)]);
 879              }
 880          }
 881  
 882          $groupColumns = array_merge($groupColumns, $filteredColumns);
 883  
 884          if ($this->order)
 885          {
 886              // Remove direction suffixes
 887              $dir = [" DESC\v", " ASC\v"];
 888  
 889              $orderColumns = $this->splitSqlExpression(implode(',', $this->order->getElements()));
 890  
 891              foreach ($orderColumns as $i => $column)
 892              {
 893                  $column           = implode(' ', $column);
 894                  $orderColumns[$i] = $column = trim(str_ireplace($dir, '', "$column\v"), "\v");
 895  
 896                  if (isset($knownAliases[str_replace($nquotes, '', $column)]))
 897                  {
 898                      unset($orderColumns[$i]);
 899                  }
 900  
 901                  if (str_ireplace($aFuncs, '', $column) != $column)
 902                  {
 903                      // Do not add aggregate expression
 904                      unset($orderColumns[$i]);
 905                  }
 906              }
 907  
 908              $groupColumns = array_merge($groupColumns, $orderColumns);
 909          }
 910  
 911          // Get a unique string of all column names that need to be included in the group statement
 912          $this->group = new QueryElement('GROUP BY', array_unique($groupColumns));
 913  
 914          return $this;
 915      }
 916  
 917      /**
 918       * Split a string of sql expression into an array of individual columns.
 919       * Single line or line end comments and multi line comments are stripped off.
 920       * Always return at least one column.
 921       *
 922       * @param   string  $string  Input string of sql expression like select expression.
 923       *
 924       * @return  array[]  The columns from the input string separated into an array.
 925       *
 926       * @since   2.0.0
 927       */
 928  	protected function splitSqlExpression($string)
 929      {
 930          // Append whitespace as equivalent to the last comma
 931          $string .= ' ';
 932  
 933          $colIdx    = 0;
 934          $start     = 0;
 935          $open      = false;
 936          $openC     = 0;
 937          $comment   = false;
 938          $endString = '';
 939          $length    = \strlen($string);
 940          $columns   = [];
 941          $column    = [];
 942          $current   = '';
 943          $previous  = null;
 944          $operators = [
 945              '+' => '',
 946              '-' => '',
 947              '*' => '',
 948              '/' => '',
 949              '%' => '',
 950              '&' => '',
 951              '|' => '',
 952              '~' => '',
 953              '^' => '',
 954          ];
 955  
 956          $addBlock = function ($block) use (&$column, &$colIdx)
 957          {
 958              if (isset($column[$colIdx]))
 959              {
 960                  $column[$colIdx] .= $block;
 961              }
 962              else
 963              {
 964                  $column[$colIdx] = $block;
 965              }
 966          };
 967  
 968          for ($i = 0; $i < $length; $i++)
 969          {
 970              $current      = substr($string, $i, 1);
 971              $current2     = substr($string, $i, 2);
 972              $current3     = substr($string, $i, 3);
 973              $lenEndString = \strlen($endString);
 974              $testEnd      = substr($string, $i, $lenEndString);
 975  
 976              if ($current == '[' || $current == '"' || $current == "'" || $current2 == '--'
 977                  || ($current2 == '/*')
 978                  || ($current == '#' && $current3 != '#__')
 979                  || ($lenEndString && $testEnd == $endString))
 980              {
 981                  if ($open)
 982                  {
 983                      if ($testEnd === $endString)
 984                      {
 985                          if ($comment)
 986                          {
 987                              if ($lenEndString > 1)
 988                              {
 989                                  $i += ($lenEndString - 1);
 990                              }
 991  
 992                              // Move cursor after close tag of comment
 993                              $start   = $i + 1;
 994                              $comment = false;
 995                          }
 996                          elseif ($current == "'" || $current == ']' || $current == '"')
 997                          {
 998                              // Check for escaped quote like '', ]] or ""
 999                              $n = 1;
1000  
1001                              while ($i + $n < $length && $string[$i + $n] == $current)
1002                              {
1003                                  $n++;
1004                              }
1005  
1006                              // Jump to the last quote
1007                              $i += $n - 1;
1008  
1009                              if ($n % 2 === 0)
1010                              {
1011                                  // There is only escaped quote
1012                                  continue;
1013                              }
1014  
1015                              if ($n > 2)
1016                              {
1017                                  // The last right close quote is not escaped
1018                                  $current = $string[$i];
1019                              }
1020                          }
1021  
1022                          $open      = false;
1023                          $endString = '';
1024                      }
1025                  }
1026                  else
1027                  {
1028                      $open = true;
1029  
1030                      if ($current == '#' || $current2 == '--')
1031                      {
1032                          $endString = "\n";
1033                          $comment   = true;
1034                      }
1035                      elseif ($current2 == '/*')
1036                      {
1037                          $endString = '*/';
1038                          $comment   = true;
1039                      }
1040                      elseif ($current == '[')
1041                      {
1042                          $endString = ']';
1043                      }
1044                      else
1045                      {
1046                          $endString = $current;
1047                      }
1048  
1049                      if ($comment && $start < $i)
1050                      {
1051                          // Add string exists before comment
1052                          $addBlock(substr($string, $start, $i - $start));
1053                          $previous = $string[$i - 1];
1054                          $start    = $i;
1055                      }
1056                  }
1057              }
1058              elseif (!$open)
1059              {
1060                  if ($current == '(')
1061                  {
1062                      $openC++;
1063                      $previous = $current;
1064                  }
1065                  elseif ($current == ')')
1066                  {
1067                      $openC--;
1068                      $previous = $current;
1069                  }
1070                  elseif ($current == '.')
1071                  {
1072                      if ($i === $start && $colIdx > 0 && !isset($column[$colIdx]))
1073                      {
1074                          // Remove whitespace placed before dot
1075                          $colIdx--;
1076                      }
1077  
1078                      $previous = $current;
1079                  }
1080                  elseif ($openC === 0)
1081                  {
1082                      if (ctype_space($current))
1083                      {
1084                          // Normalize whitespace
1085                          $string[$i] = ' ';
1086  
1087                          if ($start < $i)
1088                          {
1089                              // Add text placed before whitespace
1090                              $addBlock(substr($string, $start, $i - $start));
1091                              $colIdx++;
1092                              $previous = $string[$i - 1];
1093                          }
1094                          elseif (isset($column[$colIdx]))
1095                          {
1096                              if ($colIdx > 1 || !isset($operators[$previous]))
1097                              {
1098                                  // There was whitespace after comment
1099                                  $colIdx++;
1100                              }
1101                          }
1102  
1103                          // Move cursor forward
1104                          $start = $i + 1;
1105                      }
1106                      elseif (isset($operators[$current]) && ($current !== '*' || $previous !== '.'))
1107                      {
1108                          if ($start < $i)
1109                          {
1110                              // Add text before operator
1111                              $addBlock(substr($string, $start, $i - $start));
1112                              $colIdx++;
1113                          }
1114                          elseif (!isset($column[$colIdx]) && isset($operators[$previous]))
1115                          {
1116                              // Do not create whitespace between operators
1117                              $colIdx--;
1118                          }
1119  
1120                          // Add operator
1121                          $addBlock($current);
1122                          $previous = $current;
1123                          $colIdx++;
1124  
1125                          // Move cursor forward
1126                          $start = $i + 1;
1127                      }
1128                      else
1129                      {
1130                          $previous = $current;
1131                      }
1132                  }
1133              }
1134  
1135              if (($current == ',' && !$open && $openC == 0) || $i == $length - 1)
1136              {
1137                  if ($start < $i && !$comment)
1138                  {
1139                      // Save remaining text
1140                      $addBlock(substr($string, $start, $i - $start));
1141                  }
1142  
1143                  $columns[] = $column;
1144  
1145                  // Reset values
1146                  $column   = [];
1147                  $colIdx   = 0;
1148                  $previous = null;
1149  
1150                  // Column saved, move cursor forward after comma
1151                  $start = $i + 1;
1152              }
1153          }
1154  
1155          return $columns;
1156      }
1157  
1158      /**
1159       * Method to modify a query already in string format with the needed additions to make the query limited to a particular number of
1160       * results, or start at a particular offset.
1161       *
1162       * @param   string   $query   The query in string format
1163       * @param   integer  $limit   The limit for the result set
1164       * @param   integer  $offset  The offset for the result set
1165       *
1166       * @return  string
1167       *
1168       * @since   2.0.0
1169       */
1170  	public function processLimit($query, $limit, $offset = 0)
1171      {
1172          if ($offset > 0)
1173          {
1174              // Find a position of the last comment
1175              $commentPos = strrpos($query, '/*ORDER BY (SELECT 0)*/');
1176  
1177              // If the last comment belongs to this query, not previous subquery
1178              if ($commentPos !== false && $commentPos + 2 === strripos($query, 'ORDER BY', $commentPos + 2))
1179              {
1180                  // We can not use OFFSET without ORDER BY
1181                  $query = substr_replace($query, 'ORDER BY (SELECT 0)', $commentPos, 23);
1182              }
1183  
1184              $query .= PHP_EOL . 'OFFSET ' . (int) $offset . ' ROWS';
1185  
1186              if ($limit > 0)
1187              {
1188                  $query .= PHP_EOL . 'FETCH NEXT ' . (int) $limit . ' ROWS ONLY';
1189              }
1190          }
1191          elseif ($limit > 0)
1192          {
1193              $position = stripos($query, 'SELECT');
1194              $distinct = stripos($query, 'SELECT DISTINCT');
1195  
1196              if ($position === $distinct)
1197              {
1198                  $query = substr_replace($query, 'SELECT DISTINCT TOP ' . (int) $limit, $position, 15);
1199              }
1200              else
1201              {
1202                  $query = substr_replace($query, 'SELECT TOP ' . (int) $limit, $position, 6);
1203              }
1204          }
1205  
1206          return $query;
1207      }
1208  
1209      /**
1210       * Add a query to UNION with the current query.
1211       *
1212       * Usage:
1213       * $query->union('SELECT name FROM  #__foo')
1214       * $query->union('SELECT name FROM  #__foo', true)
1215       *
1216       * @param   DatabaseQuery|string  $query     The DatabaseQuery object or string to union.
1217       * @param   boolean               $distinct  True to only return distinct rows from the union.
1218       *
1219       * @return  $this
1220       *
1221       * @since   1.0
1222       */
1223  	public function union($query, $distinct = true)
1224      {
1225          // Set up the name with parentheses, the DISTINCT flag is redundant
1226          return $this->merge($distinct ? 'UNION SELECT * FROM ()' : 'UNION ALL SELECT * FROM ()', $query);
1227      }
1228  }


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer