first commit
This commit is contained in:
@@ -0,0 +1,775 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\SQLSRV;
|
||||
|
||||
use Closure;
|
||||
use CodeIgniter\Database\BaseBuilder;
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
use CodeIgniter\Database\Exceptions\DataException;
|
||||
use CodeIgniter\Database\ResultInterface;
|
||||
|
||||
/**
|
||||
* Builder for SQLSRV
|
||||
*
|
||||
* @todo auto check for TextCastToInt
|
||||
* @todo auto check for InsertIndexValue
|
||||
* @todo replace: delete index entries before insert
|
||||
*/
|
||||
class Builder extends BaseBuilder
|
||||
{
|
||||
/**
|
||||
* ORDER BY random keyword
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $randomKeyword = [
|
||||
'NEWID()',
|
||||
'RAND(%d)',
|
||||
];
|
||||
|
||||
/**
|
||||
* Quoted identifier flag
|
||||
*
|
||||
* Whether to use SQL-92 standard quoted identifier
|
||||
* (double quotes) or brackets for identifier escaping.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $_quoted_identifier = true;
|
||||
|
||||
/**
|
||||
* Handle increment/decrement on text
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $castTextToInt = true;
|
||||
|
||||
/**
|
||||
* Handle IDENTITY_INSERT property/
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $keyPermission = false;
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* FROM tables
|
||||
*
|
||||
* Groups tables in FROM clauses if needed, so there is no confusion
|
||||
* about operator precedence.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _fromTables(): string
|
||||
{
|
||||
$from = [];
|
||||
foreach ($this->QBFrom as $value)
|
||||
{
|
||||
$from[] = $this->getFullName($value);
|
||||
}
|
||||
|
||||
return implode(', ', $from);
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate statement
|
||||
*
|
||||
* Generates a platform-specific truncate string from the supplied data
|
||||
*
|
||||
* If the database does not support the truncate() command,
|
||||
* then this method maps to 'DELETE FROM table'
|
||||
*
|
||||
* @param string $table The table name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _truncate(string $table): string
|
||||
{
|
||||
return 'TRUNCATE TABLE ' . $this->getFullName($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* JOIN
|
||||
*
|
||||
* Generates the JOIN portion of the query
|
||||
*
|
||||
* @param string $table
|
||||
* @param string $cond The join condition
|
||||
* @param string $type The type of join
|
||||
* @param boolean $escape Whether not to try to escape identifiers
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function join(string $table, string $cond, string $type = '', bool $escape = null)
|
||||
{
|
||||
if ($type !== '')
|
||||
{
|
||||
$type = strtoupper(trim($type));
|
||||
|
||||
if (! in_array($type, $this->joinTypes, true))
|
||||
{
|
||||
$type = '';
|
||||
}
|
||||
else
|
||||
{
|
||||
$type .= ' ';
|
||||
}
|
||||
}
|
||||
|
||||
// Extract any aliases that might exist. We use this information
|
||||
// in the protectIdentifiers to know whether to add a table prefix
|
||||
$this->trackAliases($table);
|
||||
|
||||
if (! is_bool($escape))
|
||||
{
|
||||
$escape = $this->db->protectIdentifiers;
|
||||
}
|
||||
|
||||
if (! $this->hasOperator($cond))
|
||||
{
|
||||
$cond = ' USING (' . ($escape ? $this->db->escapeIdentifiers($cond) : $cond) . ')';
|
||||
}
|
||||
elseif ($escape === false)
|
||||
{
|
||||
$cond = ' ON ' . $cond;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Split multiple conditions
|
||||
if (preg_match_all('/\sAND\s|\sOR\s/i', $cond, $joints, PREG_OFFSET_CAPTURE))
|
||||
{
|
||||
$conditions = [];
|
||||
$joints = $joints[0];
|
||||
array_unshift($joints, ['', 0]);
|
||||
|
||||
for ($i = count($joints) - 1, $pos = strlen($cond); $i >= 0; $i --)
|
||||
{
|
||||
$joints[$i][1] += strlen($joints[$i][0]); // offset
|
||||
$conditions[$i] = substr($cond, $joints[$i][1], $pos - $joints[$i][1]);
|
||||
$pos = $joints[$i][1] - strlen($joints[$i][0]);
|
||||
$joints[$i] = $joints[$i][0];
|
||||
}
|
||||
ksort($conditions);
|
||||
}
|
||||
else
|
||||
{
|
||||
$conditions = [$cond];
|
||||
$joints = [''];
|
||||
}
|
||||
|
||||
$cond = ' ON ';
|
||||
foreach ($conditions as $i => $condition)
|
||||
{
|
||||
$operator = $this->getOperator($condition);
|
||||
|
||||
$cond .= $joints[$i];
|
||||
$cond .= preg_match("/(\(*)?([\[\]\w\.'-]+)" . preg_quote($operator) . '(.*)/i', $condition, $match) ? $match[1] . $this->db->protectIdentifiers($match[2]) . $operator . $this->db->protectIdentifiers($match[3]) : $condition;
|
||||
}
|
||||
}
|
||||
|
||||
// Do we want to escape the table name?
|
||||
if ($escape === true)
|
||||
{
|
||||
$table = $this->db->protectIdentifiers($table, true, null, false);
|
||||
}
|
||||
|
||||
// Assemble the JOIN statement
|
||||
$this->QBJoin[] = $join = $type . 'JOIN ' . $this->getFullName($table) . $cond;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert statement
|
||||
*
|
||||
* Generates a platform-specific insert string from the supplied data
|
||||
* auto-enable
|
||||
*
|
||||
* @todo implement check for this instad static $insertKeyPermission
|
||||
*
|
||||
* @param string $table The table name
|
||||
* @param array $keys The insert keys
|
||||
* @param array $unescapedKeys The insert values
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _insert(string $table, array $keys, array $unescapedKeys): string
|
||||
{
|
||||
$fullTableName = $this->getFullName($table);
|
||||
|
||||
// insert statement
|
||||
$statement = 'INSERT INTO ' . $fullTableName . ' (' . implode(',', $keys) . ') VALUES (' . implode(', ', $unescapedKeys) . ')';
|
||||
|
||||
return $this->keyPermission ? $this->addIdentity($fullTableName, $statement) : $statement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update statement
|
||||
*
|
||||
* Generates a platform-specific update string from the supplied data
|
||||
*
|
||||
* @param string $table the Table name
|
||||
* @param array $values the Update data
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _update(string $table, array $values): string
|
||||
{
|
||||
$valstr = [];
|
||||
|
||||
foreach ($values as $key => $val)
|
||||
{
|
||||
$valstr[] = $key . ' = ' . $val;
|
||||
}
|
||||
|
||||
$fullTableName = $this->getFullName($table);
|
||||
|
||||
$statement = 'UPDATE ' . (empty($this->QBLimit) ? '' : 'TOP(' . $this->QBLimit . ') ') . $fullTableName . ' SET '
|
||||
. implode(', ', $valstr) . $this->compileWhereHaving('QBWhere') . $this->compileOrderBy();
|
||||
|
||||
return $this->keyPermission ? $this->addIdentity($fullTableName, $statement) : $statement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments a numeric column by the specified value.
|
||||
*
|
||||
* @param string $column
|
||||
* @param integer $value
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function increment(string $column, int $value = 1)
|
||||
{
|
||||
$column = $this->db->protectIdentifiers($column);
|
||||
|
||||
if ($this->castTextToInt)
|
||||
{
|
||||
$values = [$column => "CONVERT(VARCHAR(MAX),CONVERT(INT,CONVERT(VARCHAR(MAX), {$column})) + {$value})"];
|
||||
}
|
||||
else
|
||||
{
|
||||
$values = [$column => "{$column} + {$value}"];
|
||||
}
|
||||
$sql = $this->_update($this->QBFrom[0], $values);
|
||||
|
||||
return $this->db->query($sql, $this->binds, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements a numeric column by the specified value.
|
||||
*
|
||||
* @param string $column
|
||||
* @param integer $value
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function decrement(string $column, int $value = 1)
|
||||
{
|
||||
$column = $this->db->protectIdentifiers($column);
|
||||
|
||||
if ($this->castTextToInt)
|
||||
{
|
||||
$values = [$column => "CONVERT(VARCHAR(MAX),CONVERT(INT,CONVERT(VARCHAR(MAX), {$column})) - {$value})"];
|
||||
}
|
||||
else
|
||||
{
|
||||
$values = [$column => "{$column} + {$value}"];
|
||||
}
|
||||
$sql = $this->_update($this->QBFrom[0], $values);
|
||||
|
||||
return $this->db->query($sql, $this->binds, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full name of the table
|
||||
*
|
||||
* @param string $table
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getFullName(string $table): string
|
||||
{
|
||||
$alias = '';
|
||||
|
||||
if (strpos($table, ' ') !== false)
|
||||
{
|
||||
$alias = explode(' ', $table);
|
||||
$table = array_shift($alias);
|
||||
$alias = ' ' . implode(' ', $alias);
|
||||
}
|
||||
|
||||
if ($this->db->escapeChar === '"')
|
||||
{
|
||||
return '"' . $this->db->getDatabase() . '"."' . $this->db->schema . '"."' . str_replace('"', '', $table) . '"' . $alias;
|
||||
}
|
||||
|
||||
return '[' . $this->db->getDatabase() . '].[' . $this->db->schema . '].[' . str_replace('"', '', $table) . ']' . str_replace('"', '', $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add permision statements for index value inserts
|
||||
*
|
||||
* @param string $fullTable full table name
|
||||
* @param string $insert statement
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function addIdentity(string $fullTable, string $insert): string
|
||||
{
|
||||
return 'SET IDENTITY_INSERT ' . $fullTable . " ON\n" . $insert . "\nSET IDENTITY_INSERT " . $fullTable . ' OFF';
|
||||
}
|
||||
|
||||
/**
|
||||
* Local implementation of limit
|
||||
*
|
||||
* @param string $sql
|
||||
* @param boolean $offsetIgnore
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _limit(string $sql, bool $offsetIgnore = false): string
|
||||
{
|
||||
if (empty($this->QBOrderBy))
|
||||
{
|
||||
$sql .= ' ORDER BY (SELECT NULL) ';
|
||||
}
|
||||
|
||||
if ($offsetIgnore)
|
||||
{
|
||||
$sql .= ' OFFSET 0 ';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sql .= is_int($this->QBOffset) ? ' OFFSET ' . $this->QBOffset : ' OFFSET 0 ';
|
||||
}
|
||||
|
||||
return $sql .= ' ROWS FETCH NEXT ' . $this->QBLimit . ' ROWS ONLY ';
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace
|
||||
*
|
||||
* Compiles a replace into string and runs the query
|
||||
*
|
||||
* @param array|null $set An associative array of insert values
|
||||
*
|
||||
* @return mixed
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function replace(array $set = null)
|
||||
{
|
||||
if ($set !== null)
|
||||
{
|
||||
$this->set($set);
|
||||
}
|
||||
|
||||
if (empty($this->QBSet))
|
||||
{
|
||||
if (CI_DEBUG)
|
||||
{
|
||||
throw new DatabaseException('You must use the "set" method to update an entry.');
|
||||
}
|
||||
|
||||
return false; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$table = $this->QBFrom[0];
|
||||
|
||||
$sql = $this->_replace($table, array_keys($this->QBSet), array_values($this->QBSet));
|
||||
|
||||
$this->resetWrite();
|
||||
|
||||
if ($this->testMode)
|
||||
{
|
||||
return $sql;
|
||||
}
|
||||
|
||||
$this->db->simpleQuery('SET IDENTITY_INSERT ' . $this->db->escapeIdentifiers($table) . ' ON');
|
||||
$result = $this->db->query($sql, $this->binds, false);
|
||||
$this->db->simpleQuery('SET IDENTITY_INSERT ' . $this->db->escapeIdentifiers($table) . ' OFF');
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace statement
|
||||
*
|
||||
* Generates a platform-specific replace string from the supplied data
|
||||
*
|
||||
* on match delete and insert
|
||||
*
|
||||
* @param string $table The table name
|
||||
* @param array $keys The insert keys
|
||||
* @param array $values The insert values
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _replace(string $table, array $keys, array $values): string
|
||||
{
|
||||
// check whether the existing keys are part of the primary key.
|
||||
// if so then use them for the "ON" part and exclude them from the $values and $keys
|
||||
$pKeys = $this->db->getIndexData($table);
|
||||
$keyFields = [];
|
||||
|
||||
foreach ($pKeys as $key)
|
||||
{
|
||||
if ($key->type === 'PRIMARY')
|
||||
{
|
||||
$keyFields = array_merge($keyFields, $key->fields);
|
||||
}
|
||||
|
||||
if ($key->type === 'UNIQUE')
|
||||
{
|
||||
$keyFields = array_merge($keyFields, $key->fields);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the unique field names
|
||||
$escKeyFields = array_map(function (string $field): string {
|
||||
return $this->db->protectIdentifiers($field);
|
||||
}, array_values(array_unique($keyFields)));
|
||||
|
||||
// Get the binds
|
||||
$binds = $this->binds;
|
||||
array_walk($binds, function (&$item) {
|
||||
$item = $item[0];
|
||||
});
|
||||
|
||||
// Get the common field and values from the keys data and index fields
|
||||
$common = array_intersect($keys, $escKeyFields);
|
||||
$bingo = [];
|
||||
|
||||
foreach ($common as $v)
|
||||
{
|
||||
$k = array_search($v, $escKeyFields, true);
|
||||
|
||||
$bingo[$keyFields[$k]] = $binds[trim($values[$k], ':')];
|
||||
}
|
||||
|
||||
// Querying existing data
|
||||
$builder = $this->db->table($table);
|
||||
|
||||
foreach ($bingo as $k => $v)
|
||||
{
|
||||
$builder->where($k, $v);
|
||||
}
|
||||
|
||||
$q = $builder->get()->getResult();
|
||||
|
||||
// Delete entries if we find them
|
||||
if ($q !== [])
|
||||
{
|
||||
$delete = $this->db->table($table);
|
||||
|
||||
foreach ($bingo as $k => $v)
|
||||
{
|
||||
$delete->where($k, $v);
|
||||
}
|
||||
|
||||
$delete->delete();
|
||||
}
|
||||
|
||||
return sprintf('INSERT INTO %s (%s) VALUES (%s);', $this->getFullName($table), implode(',', $keys), implode(',', $values));
|
||||
}
|
||||
|
||||
/**
|
||||
* SELECT [MAX|MIN|AVG|SUM|COUNT]()
|
||||
*
|
||||
* Handle float return value
|
||||
*
|
||||
* @param string $select Field name
|
||||
* @param string $alias
|
||||
* @param string $type
|
||||
*
|
||||
* @return BaseBuilder
|
||||
*/
|
||||
protected function maxMinAvgSum(string $select = '', string $alias = '', string $type = 'MAX')
|
||||
{
|
||||
// int functions can be handled by parent
|
||||
if ($type !== 'AVG')
|
||||
{
|
||||
return parent::maxMinAvgSum($select, $alias, $type);
|
||||
}
|
||||
|
||||
if ($select === '')
|
||||
{
|
||||
throw DataException::forEmptyInputGiven('Select');
|
||||
}
|
||||
|
||||
if (strpos($select, ',') !== false)
|
||||
{
|
||||
throw DataException::forInvalidArgument('Column name not separated by comma');
|
||||
}
|
||||
|
||||
if ($alias === '')
|
||||
{
|
||||
$alias = $this->createAliasFromTable(trim($select));
|
||||
}
|
||||
|
||||
$sql = $type . '( CAST( ' . $this->db->protectIdentifiers(trim($select)) . ' AS FLOAT ) ) AS ' . $this->db->escapeIdentifiers(trim($alias));
|
||||
|
||||
$this->QBSelect[] = $sql;
|
||||
$this->QBNoEscape[] = null;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete statement
|
||||
*
|
||||
* @param string $table The table name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _delete(string $table): string
|
||||
{
|
||||
return 'DELETE' . (empty($this->QBLimit) ? '' : ' TOP (' . $this->QBLimit . ') ') . ' FROM ' . $this->getFullName($table) . $this->compileWhereHaving('QBWhere');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete
|
||||
*
|
||||
* Compiles a delete string and runs the query
|
||||
*
|
||||
* @param mixed $where The where clause
|
||||
* @param integer $limit The limit clause
|
||||
* @param boolean $resetData
|
||||
*
|
||||
* @return mixed
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function delete($where = '', int $limit = null, bool $resetData = true)
|
||||
{
|
||||
$table = $this->db->protectIdentifiers($this->QBFrom[0], true, null, false);
|
||||
|
||||
if ($where !== '')
|
||||
{
|
||||
$this->where($where);
|
||||
}
|
||||
|
||||
if (empty($this->QBWhere))
|
||||
{
|
||||
if (CI_DEBUG)
|
||||
{
|
||||
throw new DatabaseException('Deletes are not allowed unless they contain a "where" or "like" clause.');
|
||||
}
|
||||
|
||||
return false; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
if (! empty($limit))
|
||||
{
|
||||
$this->QBLimit = $limit;
|
||||
}
|
||||
|
||||
$sql = $this->_delete($table);
|
||||
|
||||
if ($resetData)
|
||||
{
|
||||
$this->resetWrite();
|
||||
}
|
||||
|
||||
return $this->testMode ? $sql : $this->db->query($sql, $this->binds, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile the SELECT statement
|
||||
*
|
||||
* Generates a query string based on which functions were used.
|
||||
*
|
||||
* @param mixed $selectOverride
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function compileSelect($selectOverride = false): string
|
||||
{
|
||||
// Write the "select" portion of the query
|
||||
if ($selectOverride !== false)
|
||||
{
|
||||
$sql = $selectOverride;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sql = (! $this->QBDistinct) ? 'SELECT ' : 'SELECT DISTINCT ';
|
||||
|
||||
// SQL Server can't work with select * if group by is specified
|
||||
if (empty($this->QBSelect) && ! empty($this->QBGroupBy) && is_array($this->QBGroupBy))
|
||||
{
|
||||
foreach ($this->QBGroupBy as $field)
|
||||
{
|
||||
$this->QBSelect[] = is_array($field) ? $field['field'] : $field;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($this->QBSelect))
|
||||
{
|
||||
$sql .= '*';
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cycle through the "select" portion of the query and prep each column name.
|
||||
// The reason we protect identifiers here rather than in the select() function
|
||||
// is because until the user calls the from() function we don't know if there are aliases
|
||||
foreach ($this->QBSelect as $key => $val)
|
||||
{
|
||||
$noEscape = $this->QBNoEscape[$key] ?? null;
|
||||
$this->QBSelect[$key] = $this->db->protectIdentifiers($val, false, $noEscape);
|
||||
}
|
||||
|
||||
$sql .= implode(', ', $this->QBSelect);
|
||||
}
|
||||
}
|
||||
|
||||
// Write the "FROM" portion of the query
|
||||
if (! empty($this->QBFrom))
|
||||
{
|
||||
$sql .= "\nFROM " . $this->_fromTables();
|
||||
}
|
||||
|
||||
// Write the "JOIN" portion of the query
|
||||
if (! empty($this->QBJoin))
|
||||
{
|
||||
$sql .= "\n" . implode("\n", $this->QBJoin);
|
||||
}
|
||||
|
||||
$sql .= $this->compileWhereHaving('QBWhere')
|
||||
. $this->compileGroupBy()
|
||||
. $this->compileWhereHaving('QBHaving')
|
||||
. $this->compileOrderBy(); // ORDER BY
|
||||
// LIMIT
|
||||
if ($this->QBLimit)
|
||||
{
|
||||
return $sql = $this->_limit($sql . "\n");
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* WHERE, HAVING
|
||||
*
|
||||
* @param string $qbKey 'QBWhere' or 'QBHaving'
|
||||
* @param mixed $key
|
||||
* @param mixed $value
|
||||
* @param string $type
|
||||
* @param boolean $escape
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function whereHaving(string $qbKey, $key, $value = null, string $type = 'AND ', bool $escape = null)
|
||||
{
|
||||
if (! is_array($key))
|
||||
{
|
||||
$key = [$key => $value];
|
||||
}
|
||||
|
||||
// If the escape value was not set will base it on the global setting
|
||||
if (! is_bool($escape))
|
||||
{
|
||||
$escape = $this->db->protectIdentifiers;
|
||||
}
|
||||
|
||||
foreach ($key as $k => $v)
|
||||
{
|
||||
$prefix = empty($this->$qbKey) ? $this->groupGetType('') : $this->groupGetType($type);
|
||||
|
||||
if ($v !== null)
|
||||
{
|
||||
$op = $this->getOperator($k, true);
|
||||
|
||||
if (! empty($op))
|
||||
{
|
||||
$k = trim($k);
|
||||
|
||||
end($op);
|
||||
|
||||
$op = trim(current($op));
|
||||
|
||||
if (substr($k, -1 * strlen($op)) === $op)
|
||||
{
|
||||
$k = rtrim(strrev(preg_replace(strrev('/' . $op . '/'), strrev(''), strrev($k), 1)));
|
||||
}
|
||||
}
|
||||
|
||||
$bind = $this->setBind($k, $v, $escape);
|
||||
|
||||
if (empty($op))
|
||||
{
|
||||
$k .= ' =';
|
||||
}
|
||||
else
|
||||
{
|
||||
$k .= " $op";
|
||||
}
|
||||
|
||||
if ($v instanceof Closure)
|
||||
{
|
||||
$builder = $this->cleanClone();
|
||||
$v = '(' . str_replace("\n", ' ', $v($builder)->getCompiledSelect()) . ')';
|
||||
}
|
||||
else
|
||||
{
|
||||
$v = " :$bind:";
|
||||
}
|
||||
}
|
||||
elseif (! $this->hasOperator($k) && $qbKey !== 'QBHaving')
|
||||
{
|
||||
// value appears not to have been set, assign the test to IS NULL
|
||||
$k .= ' IS NULL';
|
||||
}
|
||||
elseif (preg_match('/\s*(!?=|<>|IS(?:\s+NOT)?)\s*$/i', $k, $match, PREG_OFFSET_CAPTURE))
|
||||
{
|
||||
$k = substr($k, 0, $match[0][1]) . ($match[1][0] === '=' ? ' IS NULL' : ' IS NOT NULL');
|
||||
}
|
||||
|
||||
$this->{$qbKey}[] = [
|
||||
'condition' => $prefix . $k . $v,
|
||||
'escape' => $escape,
|
||||
];
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get
|
||||
*
|
||||
* Compiles the select statement based on the other functions called
|
||||
* and runs the query
|
||||
*
|
||||
* @param integer $limit The limit clause
|
||||
* @param integer $offset The offset clause
|
||||
* @param boolean $reset Are we want to clear query builder values?
|
||||
*
|
||||
* @return ResultInterface
|
||||
*/
|
||||
public function get(int $limit = null, int $offset = 0, bool $reset = true)
|
||||
{
|
||||
if (! is_null($limit))
|
||||
{
|
||||
$this->limit($limit, $offset);
|
||||
}
|
||||
|
||||
$result = $this->testMode ? $this->getCompiledSelect($reset) : $this->db->query($this->compileSelect(), $this->binds, false);
|
||||
|
||||
if ($reset)
|
||||
{
|
||||
$this->resetSelect();
|
||||
|
||||
// Clear our binds so we don't eat up memory
|
||||
$this->binds = [];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,605 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\SQLSRV;
|
||||
|
||||
use CodeIgniter\Database\BaseConnection;
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
use Exception;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Connection for SQLSRV
|
||||
*/
|
||||
class Connection extends BaseConnection
|
||||
{
|
||||
/**
|
||||
* Database driver
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $DBDriver = 'SQLSRV';
|
||||
|
||||
/**
|
||||
* Database name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $database;
|
||||
|
||||
/**
|
||||
* Scrollable flag
|
||||
*
|
||||
* Determines what cursor type to use when executing queries.
|
||||
*
|
||||
* FALSE or SQLSRV_CURSOR_FORWARD would increase performance,
|
||||
* but would disable num_rows() (and possibly insert_id())
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $scrollable;
|
||||
|
||||
/**
|
||||
* Identifier escape character
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $escapeChar = '"';
|
||||
|
||||
/**
|
||||
* Database schema
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $schema = 'dbo';
|
||||
|
||||
/**
|
||||
* Quoted identifier flag
|
||||
*
|
||||
* Whether to use SQL-92 standard quoted identifier
|
||||
* (double quotes) or brackets for identifier escaping.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $_quoted_identifier = true;
|
||||
|
||||
/**
|
||||
* List of reserved identifiers
|
||||
*
|
||||
* Identifiers that must NOT be escaped.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $_reserved_identifiers = ['*'];
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param array $params
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($params)
|
||||
{
|
||||
parent::__construct($params);
|
||||
|
||||
// This is only supported as of SQLSRV 3.0
|
||||
if ($this->scrollable === null)
|
||||
{
|
||||
$this->scrollable = defined('SQLSRV_CURSOR_CLIENT_BUFFERED') ? SQLSRV_CURSOR_CLIENT_BUFFERED : false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the database.
|
||||
*
|
||||
* @param boolean $persistent
|
||||
*
|
||||
* @throws DatabaseException
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function connect(bool $persistent = false)
|
||||
{
|
||||
$charset = in_array(strtolower($this->charset), ['utf-8', 'utf8'], true) ? 'UTF-8' : SQLSRV_ENC_CHAR;
|
||||
|
||||
$connection = [
|
||||
'UID' => empty($this->username) ? '' : $this->username,
|
||||
'PWD' => empty($this->password) ? '' : $this->password,
|
||||
'Database' => $this->database,
|
||||
'ConnectionPooling' => $persistent ? 1 : 0,
|
||||
'CharacterSet' => $charset,
|
||||
'Encrypt' => $this->encrypt === true ? 1 : 0,
|
||||
'ReturnDatesAsStrings' => 1,
|
||||
];
|
||||
|
||||
// If the username and password are both empty, assume this is a
|
||||
// 'Windows Authentication Mode' connection.
|
||||
if (empty($connection['UID']) && empty($connection['PWD']))
|
||||
{
|
||||
unset($connection['UID'], $connection['PWD']);
|
||||
}
|
||||
|
||||
sqlsrv_configure('WarningsReturnAsErrors', 0);
|
||||
$this->connID = sqlsrv_connect($this->hostname, $connection);
|
||||
|
||||
if ($this->connID !== false)
|
||||
{
|
||||
// Determine how identifiers are escaped
|
||||
$query = $this->query('SELECT CASE WHEN (@@OPTIONS | 256) = @@OPTIONS THEN 1 ELSE 0 END AS qi');
|
||||
$query = $query->getResultObject();
|
||||
|
||||
$this->_quoted_identifier = empty($query) ? false : (bool) $query[0]->qi;
|
||||
$this->escapeChar = ($this->_quoted_identifier) ? '"' : ['[', ']'];
|
||||
|
||||
return $this->connID;
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
|
||||
foreach (sqlsrv_errors(SQLSRV_ERR_ERRORS) as $error)
|
||||
{
|
||||
$errors[] = preg_replace('/(\[.+\]\[.+\](?:\[.+\])?)(.+)/', '$2', $error['message']);
|
||||
}
|
||||
|
||||
throw new DatabaseException(implode("\n", $errors));
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep or establish the connection if no queries have been sent for
|
||||
* a length of time exceeding the server's idle timeout.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function reconnect()
|
||||
{
|
||||
$this->close();
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the database connection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _close()
|
||||
{
|
||||
sqlsrv_close($this->connID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Platform-dependant string escape
|
||||
*
|
||||
* @param string $str
|
||||
* @return string
|
||||
*/
|
||||
protected function _escapeString(string $str): string
|
||||
{
|
||||
return str_replace("'", "''", remove_invisible_characters($str, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert ID
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function insertID(): int
|
||||
{
|
||||
return $this->query('SELECT SCOPE_IDENTITY() AS insert_id')->getRow()->insert_id ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the SQL for listing tables in a platform-dependent manner.
|
||||
*
|
||||
* @param boolean $prefixLimit
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _listTables(bool $prefixLimit = false): string
|
||||
{
|
||||
$sql = 'SELECT [TABLE_NAME] AS "name"'
|
||||
. ' FROM [INFORMATION_SCHEMA].[TABLES] '
|
||||
. ' WHERE '
|
||||
. " [TABLE_SCHEMA] = '" . $this->schema . "' ";
|
||||
|
||||
if ($prefixLimit === true && $this->DBPrefix !== '')
|
||||
{
|
||||
$sql .= " AND [TABLE_NAME] LIKE '" . $this->escapeLikeString($this->DBPrefix) . "%' "
|
||||
. sprintf($this->likeEscapeStr, $this->likeEscapeChar);
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a platform-specific query string so that the column names can be fetched.
|
||||
*
|
||||
* @param string $table
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _listColumns(string $table = ''): string
|
||||
{
|
||||
return 'SELECT [COLUMN_NAME] '
|
||||
. ' FROM [INFORMATION_SCHEMA].[COLUMNS]'
|
||||
. ' WHERE [TABLE_NAME] = ' . $this->escape($this->DBPrefix . $table)
|
||||
. ' AND [TABLE_SCHEMA] = ' . $this->escape($this->schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of objects with index data
|
||||
*
|
||||
* @param string $table
|
||||
* @return stdClass[]
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function _indexData(string $table): array
|
||||
{
|
||||
$sql = 'EXEC sp_helpindex ' . $this->escape($table);
|
||||
|
||||
if (($query = $this->query($sql)) === false)
|
||||
{
|
||||
throw new DatabaseException(lang('Database.failGetIndexData'));
|
||||
}
|
||||
$query = $query->getResultObject();
|
||||
|
||||
$retVal = [];
|
||||
foreach ($query as $row)
|
||||
{
|
||||
$obj = new stdClass();
|
||||
$obj->name = $row->index_name;
|
||||
|
||||
$_fields = explode(',', trim($row->index_keys));
|
||||
$obj->fields = array_map(function ($v) {
|
||||
return trim($v);
|
||||
}, $_fields);
|
||||
|
||||
if (strpos($row->index_description, 'primary key located on') !== false)
|
||||
{
|
||||
$obj->type = 'PRIMARY';
|
||||
}
|
||||
else
|
||||
{
|
||||
$obj->type = (strpos($row->index_description, 'nonclustered, unique') !== false) ? 'UNIQUE' : 'INDEX';
|
||||
}
|
||||
|
||||
$retVal[$obj->name] = $obj;
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of objects with Foreign key data
|
||||
* referenced_object_id parent_object_id
|
||||
*
|
||||
* @param string $table
|
||||
* @return stdClass[]
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function _foreignKeyData(string $table): array
|
||||
{
|
||||
$sql = 'SELECT '
|
||||
. 'f.name as constraint_name, '
|
||||
. 'OBJECT_NAME (f.parent_object_id) as table_name, '
|
||||
. 'COL_NAME(fc.parent_object_id,fc.parent_column_id) column_name, '
|
||||
. 'OBJECT_NAME(f.referenced_object_id) foreign_table_name, '
|
||||
. 'COL_NAME(fc.referenced_object_id,fc.referenced_column_id) foreign_column_name '
|
||||
. 'FROM '
|
||||
. 'sys.foreign_keys AS f '
|
||||
. 'INNER JOIN '
|
||||
. 'sys.foreign_key_columns AS fc '
|
||||
. 'ON f.OBJECT_ID = fc.constraint_object_id '
|
||||
. 'INNER JOIN '
|
||||
. 'sys.tables t '
|
||||
. 'ON t.OBJECT_ID = fc.referenced_object_id '
|
||||
. 'WHERE '
|
||||
. 'OBJECT_NAME (f.parent_object_id) = ' . $this->escape($table);
|
||||
|
||||
if (($query = $this->query($sql)) === false)
|
||||
{
|
||||
throw new DatabaseException(lang('Database.failGetForeignKeyData'));
|
||||
}
|
||||
$query = $query->getResultObject();
|
||||
|
||||
$retVal = [];
|
||||
foreach ($query as $row)
|
||||
{
|
||||
$obj = new stdClass();
|
||||
$obj->constraint_name = $row->constraint_name;
|
||||
$obj->table_name = $row->table_name;
|
||||
$obj->column_name = $row->column_name;
|
||||
$obj->foreign_table_name = $row->foreign_table_name;
|
||||
$obj->foreign_column_name = $row->foreign_column_name;
|
||||
$retVal[] = $obj;
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables foreign key checks temporarily.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _disableForeignKeyChecks()
|
||||
{
|
||||
return 'EXEC sp_MSforeachtable "ALTER TABLE ? NOCHECK CONSTRAINT ALL"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables foreign key checks temporarily.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _enableForeignKeyChecks()
|
||||
{
|
||||
return 'EXEC sp_MSforeachtable "ALTER TABLE ? WITH CHECK CHECK CONSTRAINT ALL"';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of objects with field data
|
||||
*
|
||||
* @param string $table
|
||||
* @return stdClass[]
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
public function _fieldData(string $table): array
|
||||
{
|
||||
$sql = 'SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, COLUMN_DEFAULT
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_NAME= ' . $this->escape(($table));
|
||||
|
||||
if (($query = $this->query($sql)) === false)
|
||||
{
|
||||
throw new DatabaseException(lang('Database.failGetFieldData'));
|
||||
}
|
||||
$query = $query->getResultObject();
|
||||
|
||||
$retVal = [];
|
||||
for ($i = 0, $c = count($query); $i < $c; $i++)
|
||||
{
|
||||
$retVal[$i] = new stdClass();
|
||||
$retVal[$i]->name = $query[$i]->COLUMN_NAME;
|
||||
$retVal[$i]->type = $query[$i]->DATA_TYPE;
|
||||
$retVal[$i]->default = $query[$i]->COLUMN_DEFAULT;
|
||||
$retVal[$i]->max_length = $query[$i]->CHARACTER_MAXIMUM_LENGTH > 0 ? $query[$i]->CHARACTER_MAXIMUM_LENGTH : $query[$i]->NUMERIC_PRECISION;
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin Transaction
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function _transBegin(): bool
|
||||
{
|
||||
return (bool) sqlsrv_begin_transaction($this->connID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit Transaction
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function _transCommit(): bool
|
||||
{
|
||||
return (bool) sqlsrv_commit($this->connID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback Transaction
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function _transRollback(): bool
|
||||
{
|
||||
return (bool) sqlsrv_rollback($this->connID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last error code and message.
|
||||
* Must return this format: ['code' => string|int, 'message' => string]
|
||||
* intval(code) === 0 means "no error".
|
||||
*
|
||||
* @return array<string,string|int>
|
||||
*/
|
||||
public function error(): array
|
||||
{
|
||||
$error = [
|
||||
'code' => '00000',
|
||||
'message' => '',
|
||||
];
|
||||
|
||||
$sqlsrvErrors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
|
||||
|
||||
if (! is_array($sqlsrvErrors))
|
||||
{
|
||||
return $error;
|
||||
}
|
||||
|
||||
$sqlsrvError = array_shift($sqlsrvErrors);
|
||||
if (isset($sqlsrvError['SQLSTATE']))
|
||||
{
|
||||
$error['code'] = isset($sqlsrvError['code']) ? $sqlsrvError['SQLSTATE'] . '/' . $sqlsrvError['code'] : $sqlsrvError['SQLSTATE'];
|
||||
}
|
||||
elseif (isset($sqlsrvError['code']))
|
||||
{
|
||||
$error['code'] = $sqlsrvError['code'];
|
||||
}
|
||||
|
||||
if (isset($sqlsrvError['message']))
|
||||
{
|
||||
$error['message'] = $sqlsrvError['message'];
|
||||
}
|
||||
|
||||
return $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of rows affected by this query.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function affectedRows(): int
|
||||
{
|
||||
return sqlsrv_rows_affected($this->resultID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a specific database table to use.
|
||||
*
|
||||
* @param string|null $databaseName
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function setDatabase(string $databaseName = null)
|
||||
{
|
||||
if (empty($databaseName))
|
||||
{
|
||||
$databaseName = $this->database;
|
||||
}
|
||||
|
||||
if (empty($this->connID))
|
||||
{
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
if ($this->execute('USE ' . $this->_escapeString($databaseName)))
|
||||
{
|
||||
$this->database = $databaseName;
|
||||
$this->dataCache = [];
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the query against the database.
|
||||
*
|
||||
* @param string $sql
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function execute(string $sql)
|
||||
{
|
||||
$stmt = ($this->scrollable === false || $this->isWriteType($sql)) ?
|
||||
sqlsrv_query($this->connID, $sql) :
|
||||
sqlsrv_query($this->connID, $sql, [], ['Scrollable' => $this->scrollable]);
|
||||
|
||||
if ($stmt === false)
|
||||
{
|
||||
$error = $this->error();
|
||||
|
||||
log_message('error', $error['message']);
|
||||
if ($this->DBDebug)
|
||||
{
|
||||
throw new Exception($error['message']);
|
||||
}
|
||||
}
|
||||
|
||||
return $stmt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last error encountered by this connection.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getError()
|
||||
{
|
||||
$error = [
|
||||
'code' => '00000',
|
||||
'message' => '',
|
||||
];
|
||||
|
||||
$sqlsrvErrors = sqlsrv_errors(SQLSRV_ERR_ERRORS);
|
||||
|
||||
if (! is_array($sqlsrvErrors))
|
||||
{
|
||||
return $error;
|
||||
}
|
||||
|
||||
$sqlsrvError = array_shift($sqlsrvErrors);
|
||||
if (isset($sqlsrvError['SQLSTATE']))
|
||||
{
|
||||
$error['code'] = isset($sqlsrvError['code']) ? $sqlsrvError['SQLSTATE'] . '/' . $sqlsrvError['code'] : $sqlsrvError['SQLSTATE'];
|
||||
}
|
||||
elseif (isset($sqlsrvError['code']))
|
||||
{
|
||||
$error['code'] = $sqlsrvError['code'];
|
||||
}
|
||||
|
||||
if (isset($sqlsrvError['message']))
|
||||
{
|
||||
$error['message'] = $sqlsrvError['message'];
|
||||
}
|
||||
|
||||
return $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the platform in use (MySQLi, mssql, etc)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPlatform(): string
|
||||
{
|
||||
return $this->DBDriver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string containing the version of the database being used.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getVersion(): string
|
||||
{
|
||||
if (isset($this->dataCache['version']))
|
||||
{
|
||||
return $this->dataCache['version'];
|
||||
}
|
||||
|
||||
if (! $this->connID || empty($info = sqlsrv_server_info($this->connID)))
|
||||
{
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
return isset($info['SQLServerVersion']) ? $this->dataCache['version'] = $info['SQLServerVersion'] : false;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Determines if a query is a "write" type.
|
||||
*
|
||||
* Overrides BaseConnection::isWriteType, adding additional read query types.
|
||||
*
|
||||
* @param string $sql An SQL query string
|
||||
* @return boolean
|
||||
*/
|
||||
public function isWriteType($sql): bool
|
||||
{
|
||||
if (preg_match('/^\s*"?(EXEC\s*sp_rename)\s/i', $sql))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return parent::isWriteType($sql);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
}
|
||||
@@ -0,0 +1,410 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\SQLSRV;
|
||||
|
||||
use CodeIgniter\Database\Forge as BaseForge;
|
||||
|
||||
/**
|
||||
* Forge for SQLSRV
|
||||
*/
|
||||
class Forge extends BaseForge
|
||||
{
|
||||
/**
|
||||
* DROP CONSTRAINT statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $dropConstraintStr = 'ALTER TABLE %s DROP CONSTRAINT %s';
|
||||
|
||||
/**
|
||||
* CREATE DATABASE IF statement
|
||||
*
|
||||
* @todo missing charset, collat & check for existent
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $createDatabaseIfStr = "DECLARE @DBName VARCHAR(255) = '%s'\nDECLARE @SQL VARCHAR(max) = 'IF DB_ID( ''' + @DBName + ''' ) IS NULL CREATE DATABASE ' + @DBName\nEXEC( @SQL )";
|
||||
|
||||
/**
|
||||
* CREATE DATABASE IF statement
|
||||
*
|
||||
* @todo missing charset & collat
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $createDatabaseStr = 'CREATE DATABASE %s ';
|
||||
|
||||
/**
|
||||
* CHECK DATABASE EXIST statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $checkDatabaseExistStr = 'IF DB_ID( %s ) IS NOT NULL SELECT 1';
|
||||
|
||||
/**
|
||||
* RENAME TABLE statement
|
||||
*
|
||||
* While the below statement would work, it returns an error.
|
||||
* Also MS recommends dropping and dropping and re-creating the table.
|
||||
*
|
||||
* @see https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-rename-transact-sql?view=sql-server-2017
|
||||
* 'EXEC sp_rename %s , %s ;'
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $renameTableStr = 'EXEC sp_rename %s , %s ;';
|
||||
|
||||
/**
|
||||
* UNSIGNED support
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $unsigned = [
|
||||
'TINYINT' => 'SMALLINT',
|
||||
'SMALLINT' => 'INT',
|
||||
'INT' => 'BIGINT',
|
||||
'REAL' => 'FLOAT',
|
||||
];
|
||||
|
||||
/**
|
||||
* CREATE TABLE IF statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $createTableIfStr = "IF NOT EXISTS (SELECT * FROM sysobjects WHERE ID = object_id(N'%s') AND OBJECTPROPERTY(id, N'IsUserTable') = 1)\nCREATE TABLE";
|
||||
|
||||
/**
|
||||
* CREATE TABLE statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $createTableStr = "%s %s (%s\n) ";
|
||||
|
||||
/**
|
||||
* DROP TABLE IF statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_drop_table_if = "IF EXISTS (SELECT * FROM sysobjects WHERE ID = object_id(N'%s') AND OBJECTPROPERTY(id, N'IsUserTable') = 1)\nDROP TABLE";
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* CREATE TABLE attributes
|
||||
*
|
||||
* @param array $attributes Associative array of table attributes
|
||||
* @return string
|
||||
*/
|
||||
protected function _createTableAttributes(array $attributes): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* ALTER TABLE
|
||||
*
|
||||
* @param string $alterType ALTER type
|
||||
* @param string $table Table name
|
||||
* @param mixed $field Column definition
|
||||
*
|
||||
* @return string|string[]|false
|
||||
*/
|
||||
protected function _alterTable(string $alterType, string $table, $field)
|
||||
{
|
||||
if ($alterType === 'ADD')
|
||||
{
|
||||
return parent::_alterTable($alterType, $table, $field);
|
||||
}
|
||||
|
||||
// Handle DROP here
|
||||
if ($alterType === 'DROP')
|
||||
{
|
||||
// check if fields are part of any indexes
|
||||
$indexData = $this->db->getIndexData($table);
|
||||
|
||||
foreach ($indexData as $index)
|
||||
{
|
||||
if (is_string($field))
|
||||
{
|
||||
$field = explode(',', $field);
|
||||
}
|
||||
|
||||
$fld = array_intersect($field, $index->fields);
|
||||
|
||||
// Drop index if field is part of an index
|
||||
if (! empty($fld))
|
||||
{
|
||||
$this->_dropIndex($table, $index);
|
||||
}
|
||||
}
|
||||
|
||||
$sql = 'ALTER TABLE [' . $table . '] DROP ';
|
||||
|
||||
$fields = array_map(function ($item) {
|
||||
return 'COLUMN [' . trim($item) . ']';
|
||||
}, (array) $field);
|
||||
|
||||
return $sql .= implode(',', $fields);
|
||||
}
|
||||
|
||||
$sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table);
|
||||
|
||||
$sqls = [];
|
||||
foreach ($field as $data)
|
||||
{
|
||||
if ($data['_literal'] !== false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($data['type']))
|
||||
{
|
||||
$sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name'])
|
||||
. " {$data['type']}{$data['length']}";
|
||||
}
|
||||
|
||||
if (! empty($data['default']))
|
||||
{
|
||||
$sqls[] = $sql . ' ALTER COLUMN ADD CONSTRAINT ' . $this->db->escapeIdentifiers($data['name']) . '_def'
|
||||
. " DEFAULT {$data['default']} FOR " . $this->db->escapeIdentifiers($data['name']);
|
||||
}
|
||||
|
||||
if (isset($data['null']))
|
||||
{
|
||||
$sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name'])
|
||||
. ($data['null'] === true ? ' DROP' : '') . " {$data['type']}{$data['length']} NOT NULL";
|
||||
}
|
||||
|
||||
if (! empty($data['comment']))
|
||||
{
|
||||
$sqls[] = 'EXEC sys.sp_addextendedproperty '
|
||||
. "@name=N'Caption', @value=N'" . $data['comment'] . "' , "
|
||||
. "@level0type=N'SCHEMA',@level0name=N'" . $this->db->schema . "', "
|
||||
. "@level1type=N'TABLE',@level1name=N'" . $this->db->escapeIdentifiers($table) . "', "
|
||||
. "@level2type=N'COLUMN',@level2name=N'" . $this->db->escapeIdentifiers($data['name']) . "'";
|
||||
}
|
||||
|
||||
if (! empty($data['new_name']))
|
||||
{
|
||||
// EXEC sp_rename '[dbo].[db_misc].[value]', 'valueasdasd', 'COLUMN';
|
||||
$sqls[] = "EXEC sp_rename '[" . $this->db->schema . '].[' . $table . '].[' . $data['name'] . "]' , '" . $data['new_name'] . "', 'COLUMN';";
|
||||
}
|
||||
}
|
||||
|
||||
return $sqls;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Drop index for table
|
||||
*
|
||||
* @param string $table
|
||||
* @param object $indexData
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function _dropIndex(string $table, object $indexData)
|
||||
{
|
||||
if ($indexData->type === 'PRIMARY')
|
||||
{
|
||||
$sql = 'ALTER TABLE [' . $this->db->schema . '].[' . $table . '] DROP [' . $indexData->name . ']';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sql = 'DROP INDEX [' . $indexData->name . '] ON [' . $this->db->schema . '].[' . $table . ']';
|
||||
}
|
||||
|
||||
return $this->db->simpleQuery($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process column
|
||||
*
|
||||
* @param array $field
|
||||
* @return string
|
||||
*/
|
||||
protected function _processColumn(array $field): string
|
||||
{
|
||||
return $this->db->escapeIdentifiers($field['name'])
|
||||
. (empty($field['new_name']) ? '' : ' ' . $this->db->escapeIdentifiers($field['new_name']))
|
||||
. ' ' . $field['type'] . $field['length']
|
||||
. $field['default']
|
||||
. $field['null']
|
||||
. $field['auto_increment']
|
||||
. '' // (empty($field['comment']) ? '' : ' COMMENT ' . $field['comment'])
|
||||
. $field['unique'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process foreign keys
|
||||
*
|
||||
* @param string $table Table name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _processForeignKeys(string $table): string
|
||||
{
|
||||
$sql = '';
|
||||
|
||||
$allowActions = [
|
||||
'CASCADE',
|
||||
'SET NULL',
|
||||
'NO ACTION',
|
||||
'RESTRICT',
|
||||
'SET DEFAULT',
|
||||
];
|
||||
|
||||
if ($this->foreignKeys !== [])
|
||||
{
|
||||
foreach ($this->foreignKeys as $field => $fkey)
|
||||
{
|
||||
$nameIndex = $table . '_' . $field . '_foreign';
|
||||
|
||||
$sql .= ",\n\t CONSTRAINT " . $this->db->escapeIdentifiers($nameIndex)
|
||||
. ' FOREIGN KEY (' . $this->db->escapeIdentifiers($field) . ') '
|
||||
. ' REFERENCES ' . $this->db->escapeIdentifiers($this->db->getPrefix() . $fkey['table']) . ' (' . $this->db->escapeIdentifiers($fkey['field']) . ')';
|
||||
|
||||
if ($fkey['onDelete'] !== false && in_array($fkey['onDelete'], $allowActions, true))
|
||||
{
|
||||
$sql .= ' ON DELETE ' . $fkey['onDelete'];
|
||||
}
|
||||
|
||||
if ($fkey['onUpdate'] !== false && in_array($fkey['onUpdate'], $allowActions, true))
|
||||
{
|
||||
$sql .= ' ON UPDATE ' . $fkey['onUpdate'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process primary keys
|
||||
*
|
||||
* @param string $table Table name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _processPrimaryKeys(string $table): string
|
||||
{
|
||||
for ($i = 0, $c = count($this->primaryKeys); $i < $c; $i++)
|
||||
{
|
||||
if (! isset($this->fields[$this->primaryKeys[$i]]))
|
||||
{
|
||||
unset($this->primaryKeys[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->primaryKeys !== [])
|
||||
{
|
||||
$sql = ",\n\tCONSTRAINT " . $this->db->escapeIdentifiers('pk_' . $table)
|
||||
. ' PRIMARY KEY(' . implode(', ', $this->db->escapeIdentifiers($this->primaryKeys)) . ')';
|
||||
}
|
||||
|
||||
return $sql ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Field attribute TYPE
|
||||
*
|
||||
* Performs a data type mapping between different databases.
|
||||
*
|
||||
* @param array $attributes
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _attributeType(array &$attributes)
|
||||
{
|
||||
// Reset field lengths for data types that don't support it
|
||||
if (isset($attributes['CONSTRAINT']) && stripos($attributes['TYPE'], 'int') !== false)
|
||||
{
|
||||
$attributes['CONSTRAINT'] = null;
|
||||
}
|
||||
|
||||
switch (strtoupper($attributes['TYPE']))
|
||||
{
|
||||
case 'MEDIUMINT':
|
||||
$attributes['TYPE'] = 'INTEGER';
|
||||
$attributes['UNSIGNED'] = false;
|
||||
break;
|
||||
case 'INTEGER':
|
||||
$attributes['TYPE'] = 'INT';
|
||||
|
||||
break;
|
||||
case 'ENUM':
|
||||
$attributes['TYPE'] = 'TEXT';
|
||||
$attributes['CONSTRAINT'] = null;
|
||||
|
||||
break;
|
||||
/* case 'DATETIME':
|
||||
$attributes['TYPE'] = 'TIMESTAMP';
|
||||
break; */
|
||||
case 'TIMESTAMP':
|
||||
$attributes['TYPE'] = 'DATETIME';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Field attribute AUTO_INCREMENT
|
||||
*
|
||||
* @param array $attributes
|
||||
* @param array $field
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _attributeAutoIncrement(array &$attributes, array &$field)
|
||||
{
|
||||
if (! empty($attributes['AUTO_INCREMENT']) && $attributes['AUTO_INCREMENT'] === true && stripos($field['type'], 'INT') !== false)
|
||||
{
|
||||
$field['auto_increment'] = ' IDENTITY(1,1)';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop Table
|
||||
*
|
||||
* Generates a platform-specific DROP TABLE string
|
||||
*
|
||||
* @todo Support for cascade
|
||||
*
|
||||
* @param string $table Table name
|
||||
* @param boolean $ifExists Whether to add an IF EXISTS condition
|
||||
* @param boolean $cascade
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _dropTable(string $table, bool $ifExists, bool $cascade): string
|
||||
{
|
||||
$sql = 'DROP TABLE';
|
||||
|
||||
if ($ifExists)
|
||||
{
|
||||
$sql .= ' IF EXISTS ';
|
||||
}
|
||||
|
||||
$table = ' [' . $this->db->database . '].[' . $this->db->schema . '].[' . $table . '] ';
|
||||
|
||||
$sql .= $table;
|
||||
|
||||
if ($cascade)
|
||||
{
|
||||
$sql .= '';
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\SQLSRV;
|
||||
|
||||
use BadMethodCallException;
|
||||
use CodeIgniter\Database\BasePreparedQuery;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Prepared query for Postgre
|
||||
*/
|
||||
class PreparedQuery extends BasePreparedQuery
|
||||
{
|
||||
/**
|
||||
* Parameters array used to store the dynamic variables.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $parameters = [];
|
||||
|
||||
/**
|
||||
* The result boolean from a sqlsrv_execute.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $result;
|
||||
|
||||
/**
|
||||
* Prepares the query against the database, and saves the connection
|
||||
* info necessary to execute the query later.
|
||||
*
|
||||
* NOTE: This version is based on SQL code. Child classes should
|
||||
* override this method.
|
||||
*
|
||||
* @param string $sql
|
||||
* @param array $options Options takes an associative array;
|
||||
*
|
||||
* @return mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
public function _prepare(string $sql, array $options = [])
|
||||
{
|
||||
/* Prepare parameters for the query */
|
||||
$queryString = $this->getQueryString();
|
||||
|
||||
$parameters = $this->parameterize($queryString);
|
||||
|
||||
/* Prepare the query */
|
||||
$this->statement = sqlsrv_prepare($this->db->connID, $sql, $parameters);
|
||||
|
||||
if (! $this->statement)
|
||||
{
|
||||
$info = $this->db->error();
|
||||
$this->errorCode = $info['code'];
|
||||
$this->errorString = $info['message'];
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a new set of data and runs it against the currently
|
||||
* prepared query. Upon success, will return a Results object.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function _execute(array $data): bool
|
||||
{
|
||||
if (! isset($this->statement))
|
||||
{
|
||||
throw new BadMethodCallException('You must call prepare before trying to execute a prepared statement.');
|
||||
}
|
||||
|
||||
foreach ($data as $key => $value)
|
||||
{
|
||||
$this->parameters[$key] = $value;
|
||||
}
|
||||
|
||||
$this->result = sqlsrv_execute($this->statement);
|
||||
|
||||
return (bool) $this->result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result object for the prepared query.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function _getResult()
|
||||
{
|
||||
return $this->result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle parameters
|
||||
*
|
||||
* @param string $queryString
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parameterize(string $queryString): array
|
||||
{
|
||||
$numberOfVariables = substr_count($queryString, '?');
|
||||
|
||||
$params = [];
|
||||
|
||||
for ($c = 0; $c < $numberOfVariables; $c++)
|
||||
{
|
||||
$this->parameters[$c] = null;
|
||||
$params[] = &$this->parameters[$c];
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\SQLSRV;
|
||||
|
||||
use CodeIgniter\Database\BaseResult;
|
||||
use CodeIgniter\Entity\Entity;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Result for SQLSRV
|
||||
*/
|
||||
class Result extends BaseResult
|
||||
{
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Gets the number of fields in the result set.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getFieldCount(): int
|
||||
{
|
||||
return @sqlsrv_num_fields($this->resultID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of column names in the result set.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFieldNames(): array
|
||||
{
|
||||
$fieldNames = [];
|
||||
foreach (sqlsrv_field_metadata($this->resultID) as $field)
|
||||
{
|
||||
$fieldNames[] = $field['Name'];
|
||||
}
|
||||
|
||||
return $fieldNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of objects representing field meta-data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFieldData(): array
|
||||
{
|
||||
static $dataTypes = [
|
||||
SQLSRV_SQLTYPE_BIGINT => 'bigint',
|
||||
SQLSRV_SQLTYPE_BIT => 'bit',
|
||||
SQLSRV_SQLTYPE_CHAR => 'char',
|
||||
|
||||
SQLSRV_SQLTYPE_DATE => 'date',
|
||||
SQLSRV_SQLTYPE_DATETIME => 'datetime',
|
||||
SQLSRV_SQLTYPE_DATETIME2 => 'datetime2',
|
||||
SQLSRV_SQLTYPE_DATETIMEOFFSET => 'datetimeoffset',
|
||||
|
||||
SQLSRV_SQLTYPE_DECIMAL => 'decimal',
|
||||
SQLSRV_SQLTYPE_FLOAT => 'float',
|
||||
|
||||
SQLSRV_SQLTYPE_IMAGE => 'image',
|
||||
SQLSRV_SQLTYPE_INT => 'int',
|
||||
SQLSRV_SQLTYPE_MONEY => 'money',
|
||||
SQLSRV_SQLTYPE_NCHAR => 'nchar',
|
||||
SQLSRV_SQLTYPE_NUMERIC => 'numeric',
|
||||
|
||||
SQLSRV_SQLTYPE_NVARCHAR => 'nvarchar',
|
||||
SQLSRV_SQLTYPE_NTEXT => 'ntext',
|
||||
|
||||
SQLSRV_SQLTYPE_REAL => 'real',
|
||||
SQLSRV_SQLTYPE_SMALLDATETIME => 'smalldatetime',
|
||||
SQLSRV_SQLTYPE_SMALLINT => 'smallint',
|
||||
SQLSRV_SQLTYPE_SMALLMONEY => 'smallmoney',
|
||||
SQLSRV_SQLTYPE_TEXT => 'text',
|
||||
|
||||
SQLSRV_SQLTYPE_TIME => 'time',
|
||||
SQLSRV_SQLTYPE_TIMESTAMP => 'timestamp',
|
||||
SQLSRV_SQLTYPE_TINYINT => 'tinyint',
|
||||
SQLSRV_SQLTYPE_UNIQUEIDENTIFIER => 'uniqueidentifier',
|
||||
SQLSRV_SQLTYPE_UDT => 'udt',
|
||||
SQLSRV_SQLTYPE_VARBINARY => 'varbinary',
|
||||
SQLSRV_SQLTYPE_VARCHAR => 'varchar',
|
||||
SQLSRV_SQLTYPE_XML => 'xml',
|
||||
];
|
||||
|
||||
$retVal = [];
|
||||
foreach (sqlsrv_field_metadata($this->resultID) as $i => $field)
|
||||
{
|
||||
$retVal[$i] = new stdClass();
|
||||
$retVal[$i]->name = $field['Name'];
|
||||
$retVal[$i]->type = $field['Type'];
|
||||
$retVal[$i]->type_name = $dataTypes[$field['Type']] ?? null;
|
||||
$retVal[$i]->max_length = $field['Size'];
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees the current result.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function freeResult()
|
||||
{
|
||||
if (is_resource($this->resultID))
|
||||
{
|
||||
sqlsrv_free_stmt($this->resultID);
|
||||
$this->resultID = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the internal pointer to the desired offset. This is called
|
||||
* internally before fetching results to make sure the result set
|
||||
* starts at zero.
|
||||
*
|
||||
* @param integer $n
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function dataSeek(int $n = 0)
|
||||
{
|
||||
if ($n > 0)
|
||||
{
|
||||
for ($i = 0; $i < $n; $i++)
|
||||
{
|
||||
if (sqlsrv_fetch($this->resultID) === false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result set as an array.
|
||||
*
|
||||
* Overridden by driver classes.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function fetchAssoc()
|
||||
{
|
||||
return sqlsrv_fetch_array($this->resultID, SQLSRV_FETCH_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result set as an object.
|
||||
*
|
||||
* Overridden by child classes.
|
||||
*
|
||||
* @param string $className
|
||||
*
|
||||
* @return object|boolean|Entity
|
||||
*/
|
||||
protected function fetchObject(string $className = 'stdClass')
|
||||
{
|
||||
if (is_subclass_of($className, Entity::class))
|
||||
{
|
||||
return empty($data = $this->fetchAssoc()) ? false : (new $className())->setAttributes($data);
|
||||
}
|
||||
return sqlsrv_fetch_object($this->resultID, $className);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
/**
|
||||
* Returns the number of rows in the resultID (i.e., SQLSRV query result resource)
|
||||
*
|
||||
* @return integer Returns the number of rows retrieved on success
|
||||
*/
|
||||
public function getNumRows(): int
|
||||
{
|
||||
if (! is_int($this->numRows))
|
||||
{
|
||||
$this->numRows = sqlsrv_num_rows($this->resultID);
|
||||
}
|
||||
|
||||
return $this->numRows;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\Database\SQLSRV;
|
||||
|
||||
use CodeIgniter\Database\BaseUtils;
|
||||
use CodeIgniter\Database\Exceptions\DatabaseException;
|
||||
|
||||
/**
|
||||
* Utils for SQLSRV
|
||||
*/
|
||||
class Utils extends BaseUtils
|
||||
{
|
||||
/**
|
||||
* List databases statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $listDatabases = 'EXEC sp_helpdb'; // Can also be: EXEC sp_databases
|
||||
|
||||
/**
|
||||
* OPTIMIZE TABLE statement
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $optimizeTable = 'ALTER INDEX all ON %s REORGANIZE';
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Platform dependent version of the backup function.
|
||||
*
|
||||
* @param array|null $prefs
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function _backup(array $prefs = null)
|
||||
{
|
||||
throw new DatabaseException('Unsupported feature of the database platform you are using.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user