123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541 |
- <?php
- /**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
- */
- namespace Magento\Framework\DB;
- use Magento\Framework\App\ResourceConnection;
- use Magento\Framework\DB\Adapter\AdapterInterface;
- /**
- * Class for SQL SELECT generation and results.
- *
- * @api
- * @method \Magento\Framework\DB\Select from($name, $cols = '*', $schema = null)
- * @method \Magento\Framework\DB\Select join($name, $cond, $cols = '*', $schema = null)
- * @method \Magento\Framework\DB\Select joinInner($name, $cond, $cols = '*', $schema = null)
- * @method \Magento\Framework\DB\Select joinLeft($name, $cond, $cols = '*', $schema = null)
- * @method \Magento\Framework\DB\Select joinNatural($name, $cond, $cols = '*', $schema = null)
- * @method \Magento\Framework\DB\Select joinFull($name, $cond, $cols = '*', $schema = null)
- * @method \Magento\Framework\DB\Select joinRight($name, $cond, $cols = '*', $schema = null)
- * @method \Magento\Framework\DB\Select joinCross($name, $cols = '*', $schema = null)
- * @method \Magento\Framework\DB\Select orWhere($cond, $value = null, $type = null)
- * @method \Magento\Framework\DB\Select group($spec)
- * @method \Magento\Framework\DB\Select order($spec)
- * @method \Magento\Framework\DB\Select limitPage($page, $rowCount)
- * @method \Magento\Framework\DB\Select forUpdate($flag = true)
- * @method \Magento\Framework\DB\Select distinct($flag = true)
- * @method \Magento\Framework\DB\Select reset($part = null)
- * @method \Magento\Framework\DB\Select columns($cols = '*', $correlationName = null)
- * @since 100.0.2
- */
- class Select extends \Zend_Db_Select
- {
- /**
- * Condition type
- */
- const TYPE_CONDITION = 'TYPE_CONDITION';
- /**
- * Straight join key
- */
- const STRAIGHT_JOIN = 'straightjoin';
- /**
- * Sql straight join
- */
- const SQL_STRAIGHT_JOIN = 'STRAIGHT_JOIN';
- /**
- * @var Select\SelectRenderer
- */
- private $selectRenderer;
- /**
- * Class constructor
- * Add straight join support
- *
- * @param Adapter\Pdo\Mysql $adapter
- * @param Select\SelectRenderer $selectRenderer
- * @param array $parts
- */
- public function __construct(
- \Magento\Framework\DB\Adapter\Pdo\Mysql $adapter,
- \Magento\Framework\DB\Select\SelectRenderer $selectRenderer,
- $parts = []
- ) {
- self::$_partsInit = array_merge(self::$_partsInit, $parts);
- if (!isset(self::$_partsInit[self::STRAIGHT_JOIN])) {
- self::$_partsInit = [self::STRAIGHT_JOIN => false] + self::$_partsInit;
- }
- $this->selectRenderer = $selectRenderer;
- parent::__construct($adapter);
- }
- /**
- * Adds a WHERE condition to the query by AND.
- *
- * If a value is passed as the second param, it will be quoted
- * and replaced into the condition wherever a question-mark
- * appears. Array values are quoted and comma-separated.
- *
- * <code>
- * // simplest but non-secure
- * $select->where("id = $id");
- *
- * // secure (ID is quoted but matched anyway)
- * $select->where('id = ?', $id);
- *
- * // alternatively, with named binding
- * $select->where('id = :id');
- * </code>
- *
- * Note that it is more correct to use named bindings in your
- * queries for values other than strings. When you use named
- * bindings, don't forget to pass the values when actually
- * making a query:
- *
- * <code>
- * $db->fetchAll($select, array('id' => 5));
- * </code>
- *
- * @param string $cond The WHERE condition.
- * @param string $value OPTIONAL A single value to quote into the condition.
- * @param string|int|null $type OPTIONAL The type of the given value
- * @return \Magento\Framework\DB\Select
- */
- public function where($cond, $value = null, $type = null)
- {
- if ($value === null && $type === null) {
- $value = '';
- } elseif ($type == self::TYPE_CONDITION) {
- $type = null;
- }
- if (is_array($value)) {
- $cond = $this->getConnection()->quoteInto($cond, $value);
- $value = null;
- }
- return parent::where($cond, $value, $type);
- }
- /**
- * Reset unused LEFT JOIN(s)
- *
- * @return $this
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.NPathComplexity)
- */
- public function resetJoinLeft()
- {
- foreach ($this->_parts[self::FROM] as $tableId => $tableProp) {
- if ($tableProp['joinType'] == self::LEFT_JOIN) {
- $useJoin = false;
- foreach ($this->_parts[self::COLUMNS] as $columnEntry) {
- list($correlationName, $column) = $columnEntry;
- if ($column instanceof \Zend_Db_Expr) {
- if ($this->_findTableInCond(
- $tableId,
- $column
- ) || $this->_findTableInCond(
- $tableProp['tableName'],
- $column
- )
- ) {
- $useJoin = true;
- }
- } else {
- if ($correlationName == $tableId) {
- $useJoin = true;
- }
- }
- }
- foreach ($this->_parts[self::WHERE] as $where) {
- if ($this->_findTableInCond(
- $tableId,
- $where
- ) || $this->_findTableInCond(
- $tableProp['tableName'],
- $where
- )
- ) {
- $useJoin = true;
- }
- }
- $joinUseInCond = $useJoin;
- $joinInTables = [];
- foreach ($this->_parts[self::FROM] as $tableCorrelationName => $table) {
- if ($tableCorrelationName == $tableId) {
- continue;
- }
- if (!empty($table['joinCondition'])) {
- if ($this->_findTableInCond(
- $tableId,
- $table['joinCondition']
- ) || $this->_findTableInCond(
- $tableProp['tableName'],
- $table['joinCondition']
- )
- ) {
- $useJoin = true;
- $joinInTables[] = $tableCorrelationName;
- }
- }
- }
- if (!$useJoin) {
- unset($this->_parts[self::FROM][$tableId]);
- } else {
- $this->_parts[self::FROM][$tableId]['useInCond'] = $joinUseInCond;
- $this->_parts[self::FROM][$tableId]['joinInTables'] = $joinInTables;
- }
- }
- }
- $this->_resetJoinLeft();
- return $this;
- }
- /**
- * Validate LEFT joins, and remove it if not exists
- *
- * @return $this
- */
- protected function _resetJoinLeft()
- {
- foreach ($this->_parts[self::FROM] as $tableId => $tableProp) {
- if ($tableProp['joinType'] == self::LEFT_JOIN) {
- if ($tableProp['useInCond']) {
- continue;
- }
- $used = false;
- foreach ($tableProp['joinInTables'] as $table) {
- if (isset($this->_parts[self::FROM][$table])) {
- $used = true;
- break;
- }
- }
- if (!$used) {
- unset($this->_parts[self::FROM][$tableId]);
- return $this->_resetJoinLeft();
- }
- }
- }
- return $this;
- }
- /**
- * Find table name in condition (where, column)
- *
- * @param string $table
- * @param string $cond
- * @return bool
- */
- protected function _findTableInCond($table, $cond)
- {
- $quote = $this->_adapter->getQuoteIdentifierSymbol();
- if (strpos($cond, $quote . $table . $quote . '.') !== false) {
- return true;
- }
- $position = 0;
- $result = 0;
- $needle = [];
- while (is_integer($result)) {
- $result = strpos($cond, $table . '.', $position);
- if (is_integer($result)) {
- $needle[] = $result;
- $position = $result + strlen($table) + 1;
- }
- }
- if (!$needle) {
- return false;
- }
- foreach ($needle as $position) {
- if ($position == 0) {
- return true;
- }
- if (!preg_match('#[a-z0-9_]#is', substr($cond, $position - 1, 1))) {
- return true;
- }
- }
- return false;
- }
- /**
- * Populate the {@link $_parts} 'join' key
- *
- * Does the dirty work of populating the join key.
- *
- * The $name and $cols parameters follow the same logic
- * as described in the from() method.
- *
- * @param null|string $type Type of join; inner, left, and null are currently supported
- * @param array|string|\Zend_Db_Expr $name Table name
- * @param string $cond Join on this condition
- * @param array|string $cols The columns to select from the joined table
- * @param string $schema The database name to specify, if any.
- * @return \Magento\Framework\DB\Select This \Magento\Framework\DB\Select object
- * @throws \Zend_Db_Select_Exception
- */
- protected function _join($type, $name, $cond, $cols, $schema = null)
- {
- if ($type == self::INNER_JOIN && empty($cond)) {
- $type = self::CROSS_JOIN;
- }
- return parent::_join($type, $name, $cond, $cols, $schema);
- }
- /**
- * Sets a limit count and offset to the query.
- *
- * @param int $count OPTIONAL The number of rows to return.
- * @param int $offset OPTIONAL Start returning after this many rows.
- * @return $this
- */
- public function limit($count = null, $offset = null)
- {
- if ($count === null) {
- $this->reset(self::LIMIT_COUNT);
- } else {
- $this->_parts[self::LIMIT_COUNT] = (int)$count;
- }
- if ($offset === null) {
- $this->reset(self::LIMIT_OFFSET);
- } else {
- $this->_parts[self::LIMIT_OFFSET] = (int)$offset;
- }
- return $this;
- }
- /**
- * Cross Table Update From Current select
- *
- * @param string|array $table
- * @return string
- */
- public function crossUpdateFromSelect($table)
- {
- return $this->getConnection()->updateFromSelect($this, $table);
- }
- /**
- * Insert to table from current select
- *
- * @param string $tableName
- * @param array $fields
- * @param bool $onDuplicate
- * @return string
- */
- public function insertFromSelect($tableName, $fields = [], $onDuplicate = true)
- {
- $mode = $onDuplicate ? AdapterInterface::INSERT_ON_DUPLICATE : false;
- return $this->getConnection()->insertFromSelect($this, $tableName, $fields, $mode);
- }
- /**
- * Generate INSERT IGNORE query to the table from current select
- *
- * @param string $tableName
- * @param array $fields
- * @return string
- */
- public function insertIgnoreFromSelect($tableName, $fields = [])
- {
- return $this->getConnection()->insertFromSelect($this, $tableName, $fields, AdapterInterface::INSERT_IGNORE);
- }
- /**
- * Retrieve DELETE query from select
- *
- * @param string $table The table name or alias
- * @return string
- */
- public function deleteFromSelect($table)
- {
- return $this->getConnection()->deleteFromSelect($this, $table);
- }
- /**
- * Modify (hack) part of the structured information for the current query
- *
- * @param string $part
- * @param mixed $value
- * @return $this
- * @throws \Zend_Db_Select_Exception
- */
- public function setPart($part, $value)
- {
- $part = strtolower($part);
- if (!array_key_exists($part, $this->_parts)) {
- throw new \Zend_Db_Select_Exception("Invalid Select part '{$part}'");
- }
- $this->_parts[$part] = $value;
- return $this;
- }
- /**
- * Use a STRAIGHT_JOIN for the SQL Select
- *
- * @param bool $flag Whether or not the SELECT use STRAIGHT_JOIN (default true).
- * @return $this
- */
- public function useStraightJoin($flag = true)
- {
- $this->_parts[self::STRAIGHT_JOIN] = (bool)$flag;
- return $this;
- }
- /**
- * Render STRAIGHT_JOIN clause
- *
- * @param string $sql SQL query
- * @return string
- */
- protected function _renderStraightjoin($sql)
- {
- if ($this->_adapter->supportStraightJoin() && !empty($this->_parts[self::STRAIGHT_JOIN])) {
- $sql .= ' ' . self::SQL_STRAIGHT_JOIN;
- }
- return $sql;
- }
- /**
- * Adds to the internal table-to-column mapping array.
- *
- * @param string $correlationName The table/join the columns come from.
- * @param array|string $cols The list of columns; preferably as an array,
- * but possibly as a string containing one column.
- * @param bool|string $afterCorrelationName True if it should be prepended,
- * a correlation name if it should be inserted
- * @return void
- */
- protected function _tableCols($correlationName, $cols, $afterCorrelationName = null)
- {
- if (!is_array($cols)) {
- $cols = [$cols];
- }
- foreach ($cols as $k => $v) {
- if ($v instanceof Select) {
- $cols[$k] = new \Zend_Db_Expr(sprintf('(%s)', $v->assemble()));
- }
- }
- return parent::_tableCols($correlationName, $cols, $afterCorrelationName);
- }
- /**
- * Adds the random order to query
- *
- * @param string $field integer field name
- * @return $this
- */
- public function orderRand($field = null)
- {
- $this->_adapter->orderRand($this, $field);
- return $this;
- }
- /**
- * Render FOR UPDATE clause
- *
- * @param string $sql SQL query
- * @return string
- */
- protected function _renderForupdate($sql)
- {
- if ($this->_parts[self::FOR_UPDATE]) {
- $sql = $this->_adapter->forUpdate($sql);
- }
- return $sql;
- }
- /**
- * Add EXISTS clause
- *
- * @param Select $select
- * @param string $joinCondition
- * @param bool $isExists
- * @return $this
- */
- public function exists($select, $joinCondition, $isExists = true)
- {
- if ($isExists) {
- $exists = 'EXISTS (%s)';
- } else {
- $exists = 'NOT EXISTS (%s)';
- }
- $select->reset(self::COLUMNS)->columns([new \Zend_Db_Expr('1')])->where($joinCondition);
- $exists = sprintf($exists, $select->assemble());
- $this->where($exists);
- return $this;
- }
- /**
- * Get adapter
- *
- * @return \Magento\Framework\DB\Adapter\AdapterInterface
- */
- public function getConnection()
- {
- return $this->_adapter;
- }
- /**
- * Converts this object to an SQL SELECT string.
- *
- * @return string|null This object as a SELECT string. (or null if a string cannot be produced.)
- * @since 100.1.0
- */
- public function assemble()
- {
- return $this->selectRenderer->render($this);
- }
- /**
- * @return string[]
- * @since 100.0.11
- */
- public function __sleep()
- {
- $properties = array_keys(get_object_vars($this));
- $properties = array_diff(
- $properties,
- [
- '_adapter',
- 'selectRenderer'
- ]
- );
- return $properties;
- }
- /**
- * Init not serializable fields
- *
- * @return void
- * @since 100.0.11
- */
- public function __wakeup()
- {
- $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
- $this->_adapter = $objectManager->get(ResourceConnection::class)->getConnection();
- $this->selectRenderer = $objectManager->get(\Magento\Framework\DB\Select\SelectRenderer::class);
- }
- }
|