// +---------------------------------------------------------------------- declare (strict_types = 1); namespace think\db\builder; use PDO; use think\db\BaseQuery as Query; use think\db\Builder; use think\db\exception\DbException as Exception; use think\db\Raw; /** * mysql数据库驱动. */ class Mysql extends Builder { /** * 查询表达式解析. * * @var array */ protected $parser = [ 'parseCompare' => ['=', '!=', '<>', '>', '>=', '<', '<='], 'parseLike' => ['LIKE', 'NOT LIKE'], 'parseBetween' => ['NOT BETWEEN', 'BETWEEN'], 'parseIn' => ['NOT IN', 'IN'], 'parseExp' => ['EXP'], 'parseRegexp' => ['REGEXP', 'NOT REGEXP'], 'parseNull' => ['NOT NULL', 'NULL'], 'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'], 'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'], 'parseExists' => ['NOT EXISTS', 'EXISTS'], 'parseColumn' => ['COLUMN'], 'parseFindInSet' => ['FIND IN SET'], ]; /** * SELECT SQL表达式. * * @var string */ protected $selectSql = 'SELECT%DISTINCT%%EXTRA% %FIELD% FROM %TABLE%%PARTITION%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT% %LOCK%%COMMENT%'; /** * INSERT SQL表达式. * * @var string */ protected $insertSql = '%INSERT%%EXTRA% INTO %TABLE%%PARTITION% SET %SET% %DUPLICATE%%COMMENT%'; /** * INSERT ALL SQL表达式. * * @var string */ protected $insertAllSql = '%INSERT%%EXTRA% INTO %TABLE%%PARTITION% (%FIELD%) VALUES %DATA% %DUPLICATE%%COMMENT%'; /** * UPDATE SQL表达式. * * @var string */ protected $updateSql = 'UPDATE%EXTRA% %TABLE%%PARTITION% %JOIN% SET %SET% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; /** * DELETE SQL表达式. * * @var string */ protected $deleteSql = 'DELETE%EXTRA% FROM %TABLE%%PARTITION%%USING%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%'; /** * 生成查询SQL. * * @param Query $query 查询对象 * @param bool $one 是否仅获取一个记录 * * @return string */ public function select(Query $query, bool $one = false): string { $options = $query->getOptions(); return str_replace( ['%TABLE%', '%PARTITION%', '%DISTINCT%', '%EXTRA%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'], [ $this->parseTable($query, $options['table']), $this->parsePartition($query, $options['partition']), $this->parseDistinct($query, $options['distinct']), $this->parseExtra($query, $options['extra']), $this->parseField($query, $options['field'] ?? []), $this->parseJoin($query, $options['join']), $this->parseWhere($query, $options['where']), $this->parseGroup($query, $options['group']), $this->parseHaving($query, $options['having']), $this->parseOrder($query, $options['order']), $this->parseLimit($query, $one ? '1' : $options['limit']), $this->parseUnion($query, $options['union']), $this->parseLock($query, $options['lock']), $this->parseComment($query, $options['comment']), $this->parseForce($query, $options['force']), ], $this->selectSql ); } /** * 生成Insert SQL. * * @param Query $query 查询对象 * * @return string */ public function insert(Query $query): string { $options = $query->getOptions(); // 分析并处理数据 $data = $this->parseData($query, $options['data']); if (empty($data)) { return ''; } $set = []; foreach ($data as $key => $val) { $set[] = $key . ' = ' . $val; } return str_replace( ['%INSERT%', '%EXTRA%', '%TABLE%', '%PARTITION%', '%SET%', '%DUPLICATE%', '%COMMENT%'], [ !empty($options['replace']) ? 'REPLACE' : 'INSERT', $this->parseExtra($query, $options['extra']), $this->parseTable($query, $options['table']), $this->parsePartition($query, $options['partition']), implode(' , ', $set), $this->parseDuplicate($query, $options['duplicate']), $this->parseComment($query, $options['comment']), ], $this->insertSql ); } /** * 生成insertall SQL. * * @param Query $query 查询对象 * @param array $dataSet 数据集 * * @return string */ public function insertAll(Query $query, array $dataSet): string { $options = $query->getOptions(); $bind = $query->getFieldsBindType(); // 获取合法的字段 if (empty($options['field']) || '*' == $options['field']) { $allowFields = array_keys($bind); } else { $allowFields = $options['field']; } $fields = []; $values = []; foreach ($dataSet as $data) { $data = $this->parseData($query, $data, $allowFields, $bind); $values[] = '( ' . implode(',', array_values($data)) . ' )'; if (!isset($insertFields)) { $insertFields = array_keys($data); } } foreach ($insertFields as $field) { $fields[] = $this->parseKey($query, $field); } return str_replace( ['%INSERT%', '%EXTRA%', '%TABLE%', '%PARTITION%', '%FIELD%', '%DATA%', '%DUPLICATE%', '%COMMENT%'], [ !empty($options['replace']) ? 'REPLACE' : 'INSERT', $this->parseExtra($query, $options['extra']), $this->parseTable($query, $options['table']), $this->parsePartition($query, $options['partition']), implode(' , ', $fields), implode(' , ', $values), $this->parseDuplicate($query, $options['duplicate']), $this->parseComment($query, $options['comment']), ], $this->insertAllSql ); } /** * 生成insertall SQL * @access public * @param Query $query 查询对象 * @param array $keys 键值 * @param array $values 数据 * @return string */ public function insertAllByKeys(Query $query, array $keys, array $datas): string { $options = $query->getOptions(); $bind = $query->getFieldsBindType(); $fields = []; $values = []; foreach ($keys as $field) { $fields[] = $this->parseKey($query, $field); } foreach ($datas as $data) { foreach ($data as $key => &$val) { if (!$query->isAutoBind()) { $val = PDO::PARAM_STR == $bind[$keys[$key]] ? '\'' . $val . '\'' : $val; } else { $val = $this->parseDataBind($query, $keys[$key], $val, $bind); } } $values[] = '( ' . implode(',', $data) . ' )'; } return str_replace( ['%INSERT%', '%EXTRA%', '%TABLE%', '%PARTITION%', '%FIELD%', '%DATA%', '%DUPLICATE%', '%COMMENT%'], [ !empty($options['replace']) ? 'REPLACE' : 'INSERT', $this->parseExtra($query, $options['extra']), $this->parseTable($query, $options['table']), $this->parsePartition($query, $options['partition']), implode(' , ', $fields), implode(' , ', $values), $this->parseDuplicate($query, $options['duplicate']), $this->parseComment($query, $options['comment']), ], $this->insertAllSql ); } /** * 生成update SQL. * * @param Query $query 查询对象 * * @return string */ public function update(Query $query): string { $options = $query->getOptions(); $data = $this->parseData($query, $options['data']); if (empty($data)) { return ''; } $set = []; foreach ($data as $key => $val) { $set[] = (str_contains($key, '->') ? strstr($key, '->', true) : $key) . ' = ' . $val; } return str_replace( ['%TABLE%', '%PARTITION%', '%EXTRA%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], [ $this->parseTable($query, $options['table']), $this->parsePartition($query, $options['partition']), $this->parseExtra($query, $options['extra']), implode(' , ', $set), $this->parseJoin($query, $options['join']), $this->parseWhere($query, $options['where']), $this->parseOrder($query, $options['order']), $this->parseLimit($query, $options['limit']), $this->parseLock($query, $options['lock']), $this->parseComment($query, $options['comment']), ], $this->updateSql ); } /** * 生成delete SQL. * * @param Query $query 查询对象 * * @return string */ public function delete(Query $query): string { $options = $query->getOptions(); return str_replace( ['%TABLE%', '%PARTITION%', '%EXTRA%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], [ $this->parseTable($query, $options['table']), $this->parsePartition($query, $options['partition']), $this->parseExtra($query, $options['extra']), !empty($options['using']) ? ' USING ' . $this->parseTable($query, $options['using']) . ' ' : '', $this->parseJoin($query, $options['join']), $this->parseWhere($query, $options['where']), $this->parseOrder($query, $options['order']), $this->parseLimit($query, $options['limit']), $this->parseLock($query, $options['lock']), $this->parseComment($query, $options['comment']), ], $this->deleteSql ); } /** * 正则查询. * * @param Query $query 查询对象 * @param string $key * @param string $exp * @param mixed $value * @param string $field * * @return string */ protected function parseRegexp(Query $query, string $key, string $exp, $value, string $field): string { if ($value instanceof Raw) { $value = $this->parseRaw($query, $value); } return $key . ' ' . $exp . ' ' . $value; } /** * FIND_IN_SET 查询. * * @param Query $query 查询对象 * @param string $key * @param string $exp * @param mixed $value * @param string $field * * @return string */ protected function parseFindInSet(Query $query, string $key, string $exp, $value, string $field): string { if ($value instanceof Raw) { $value = $this->parseRaw($query, $value); } return 'FIND_IN_SET(' . $value . ', ' . $key . ')'; } /** * 字段和表名处理. * * @param Query $query 查询对象 * @param mixed $key 字段名 * @param bool $strict 严格检测 * * @return string */ public function parseKey(Query $query, string | int | Raw $key, bool $strict = false): string { if (is_int($key)) { return (string) $key; } if ($key instanceof Raw) { return $this->parseRaw($query, $key); } $key = trim($key); if (str_contains($key, '->>') && !str_contains($key, '(')) { // JSON字段支持 [$field, $name] = explode('->>', $key, 2); return $this->parseKey($query, $field, true) . '->>\'$' . (str_starts_with($name, '[') ? '' : '.') . str_replace('->>', '.', $name) . '\''; } if (str_contains($key, '->') && !str_contains($key, '(')) { // JSON字段支持 [$field, $name] = explode('->', $key, 2); return 'json_unquote(json_extract(' . $this->parseKey($query, $field, true) . ', \'$' . (str_starts_with($name, '[') ? '' : '.') . str_replace('->', '.', $name) . '\'))'; } if (str_contains($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { [$table, $key] = explode('.', $key, 2); $alias = $query->getOptions('alias'); if ('__TABLE__' == $table) { $table = $query->getOptions('table'); $table = is_array($table) ? array_shift($table) : $table; } if (isset($alias[$table])) { $table = $alias[$table]; } } if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { throw new Exception('not support data:' . $key); } if ('*' != $key && !preg_match('/[,\'\"\*\(\)`.\s]/', $key)) { $key = '`' . $key . '`'; } if (isset($table)) { if (str_contains($table, '.')) { $table = str_replace('.', '`.`', $table); } $key = '`' . $table . '`.' . $key; } return $key; } /** * Null查询. * * @param Query $query 查询对象 * @param string $key * @param string $exp * @param mixed $value * @param string $field * @param int $bindType * * @return string */ protected function parseNull(Query $query, string $key, string $exp, $value, $field, int $bindType): string { if (str_starts_with($key, "json_unquote(json_extract")) { if ('NULL' === $exp) { return '(' . $key . ' is null OR ' . $key . ' = \'null\')'; } elseif ('NOT NULL' === $exp) { return '(' . $key . ' is not null AND ' . $key . ' != \'null\')'; } } return parent::parseNull($query, $key, $exp, $value, $field, $bindType); } /** * 随机排序. * * @param Query $query 查询对象 * * @return string */ protected function parseRand(Query $query): string { return 'rand()'; } /** * Partition 分析. * * @param Query $query 查询对象 * @param string|array $partition 分区 * * @return string */ protected function parsePartition(Query $query, $partition): string { if ('' == $partition) { return ''; } if (is_string($partition)) { $partition = explode(',', $partition); } return ' PARTITION (' . implode(' , ', $partition) . ') '; } /** * ON DUPLICATE KEY UPDATE 分析. * * @param Query $query 查询对象 * @param mixed $duplicate * * @return string */ protected function parseDuplicate(Query $query, $duplicate): string { if ('' == $duplicate) { return ''; } if ($duplicate instanceof Raw) { return ' ON DUPLICATE KEY UPDATE ' . $this->parseRaw($query, $duplicate) . ' '; } if (is_string($duplicate)) { $duplicate = explode(',', $duplicate); } $updates = []; foreach ($duplicate as $key => $val) { if (is_numeric($key)) { $val = $this->parseKey($query, $val); $updates[] = $val . ' = VALUES(' . $val . ')'; } elseif ($val instanceof Raw) { $updates[] = $this->parseKey($query, $key) . ' = ' . $this->parseRaw($query, $val); } else { $name = $query->bindValue($val, $query->getConnection()->getFieldBindType($key)); $updates[] = $this->parseKey($query, $key) . ' = :' . $name; } } return ' ON DUPLICATE KEY UPDATE ' . implode(' , ', $updates) . ' '; } }