View source
<?php
namespace Drupal\Driver\Database\sqlsrv;
use Drupal\Core\Database\SchemaObjectDoesNotExistException;
use Drupal\Driver\Database\sqlsrv\PDO\Connection;
use Drupal\Driver\Database\sqlsrv\Settings\ConstraintTypes;
use Drupal\Driver\Database\sqlsrv\Settings\RecoveryModel;
class Scheme {
const COMMENT_MAX_BYTES = 7500;
const INDEX_MAX_SIZE_WITH_XML = 128;
private $cnn = null;
public function __construct(Connection $cnn) {
$this->cnn = $cnn;
}
public function DefaultValueExpression($sqlsr_type, $default) {
$result = is_string($default) ? $this->cnn
->quote($default) : $default;
if (Utils::GetMSSQLType($sqlsr_type) == 'varbinary') {
$default = $this->cnn
->quote($default);
$result = "CONVERT({$sqlsr_type}, {$default})";
}
return $result;
}
public function IndexExists($table, $index) {
return (bool) $this->cnn
->query_execute('SELECT 1 FROM sys.indexes WHERE object_id = OBJECT_ID(:table) AND name = :name', array(
':table' => $table,
':name' => $index,
))
->fetchField();
}
public function ConstraintExists($name, ConstraintTypes $type) {
return (bool) $this->cnn
->query_execute("SELECT CASE WHEN OBJECT_ID(:name, :type) IS NULL THEN 0 ELSE 1 END", array(
':type' => $type
->__toString(),
':name' => "dbo.[{$name}]",
))
->fetchField();
}
public function IndexDrop($table, $index) {
if (!$this
->IndexExists($table, $index)) {
return;
}
$this->cnn
->query_execute("DROP INDEX {$index} ON {$table}");
}
public function FieldExists($table, $column) {
return $this->cnn
->query_execute("SELECT 1 FROM INFORMATION_SCHEMA.columns WHERE table_name = '{$table}' AND column_name = '{$column}'")
->fetchField() !== false;
}
public function StatisticsDrop($table, $statistics) {
$this->cnn
->query_execute("DROP STATISTICS {$table}.{$statistics}");
}
public function StatisticsExists($table, $statistics) {
$query = <<<EOF
SELECT stat.name AS Statistics,
OBJECT_NAME(stat.object_id) AS Object,
COL_NAME(scol.object_id, scol.column_id) AS Column
FROM sys.stats AS stat (NOLOCK) Join sys.stats_columns AS scol (NOLOCK)
ON stat.stats_id = scol.stats_id AND stat.object_id = scol.object_id
INNER JOIN sys.tables AS tab (NOLOCK) on tab.object_id = stat.object_id
WHERE OBJECT_NAME(stat.object_id) = :table AND
stat.name = :statistics
EOF;
return $this->cnn
->query_execute($query, array(
':table' => $table,
':statistics' => $statistics,
))
->fetchField() !== false;
}
public function TriggerExists($name) {
return $this->cnn
->query_execute("SELECT 1 FROM sys.triggers WHERE name = :name", array(
':name' => $name,
))
->fetchField() !== false;
}
public function TriggeDrop($name) {
$this->cnn
->query_execute("DROP TRGGER {$name}");
}
public function ViewExists($name) {
return $this->cnn
->query_execute("SELECT 1 FROM INFORMATION_SCHEMA.views WHERE table_name = :name", array(
':name' => $name,
))
->fetchField() !== false;
}
public function ViewDrop($name) {
$this->cnn
->query_execute("DROP VIEW {$name}");
}
public function FunctionDrop($name) {
$this->cnn
->query_execute("DROP FUNCTION {$name}");
}
public function TableExists($table, $refresh = false) {
if (empty($table)) {
return false;
}
$bin = $this->cnn
->Cache('sqlsrv-table-exists');
if (!$bin
->Get('@@preloaded')) {
foreach ($this->cnn
->query_execute("SELECT table_name FROM INFORMATION_SCHEMA.tables") as $t) {
$bin
->Set($t->table_name, true);
}
$bin
->Set('@@preloaded', true);
}
if (!$refresh && ($cache = $bin
->Get($table))) {
return $cache->data;
}
$query = null;
if ($table[0] == '#') {
$table .= '%';
$query = "SELECT 1 FROM tempdb.sys.tables WHERE name like :table";
}
else {
$query = "SELECT 1 FROM INFORMATION_SCHEMA.tables WHERE table_name = :table";
}
$exists = $this->cnn
->query_execute($query, [
':table' => $table,
])
->fetchField() !== false;
if ($exists) {
$bin
->Set($table, $exists);
}
return $exists;
}
public function TableDrop($table) {
if (!$this
->TableExists($table, true)) {
return false;
}
$this->cnn
->query_execute("DROP TABLE [{$table}]");
$this->cnn
->Cache('sqlsrv-table-exists')
->Clear($table);
return true;
}
public function TableHasXmlIndex($table) {
$info = $this
->TableDetailsGet($table);
if (isset($info['indexes']) && is_array($info['indexes'])) {
foreach ($info['indexes'] as $name => $index) {
if (strcasecmp($index['type_desc'], 'XML') === 0) {
return $name;
}
}
}
return false;
}
public function GetDefaultSchema() {
if ($cache = $this->cnn
->Cache('sqlsrv-engine')
->Get('default_schema')) {
return $cache->data;
}
$result = $this->cnn
->query_execute("SELECT SCHEMA_NAME()")
->fetchField();
$this->cnn
->Cache('sqlsrv-engine')
->Set('default_schema', $result);
return $result;
}
public function removeSQLComments($sql, &$comments = null) {
$sqlComments = '@(([\'"]).*?[^\\\\]\\2)|((?:\\#|--).*?$|/\\*(?:[^/*]|/(?!\\*)|\\*(?!/)|(?R))*\\*\\/)\\s*|(?<=;)\\s+@ms';
$uncommentedSQL = trim(preg_replace($sqlComments, '$1', $sql));
if (is_array($comments)) {
preg_match_all($sqlComments, $sql, $comments);
$comments = array_filter($comments[3]);
}
return $uncommentedSQL;
}
public function getDriverAttributes() {
return new \Drupal\Driver\Database\sqlsrv\Scheme\DriverAttributes($this->cnn);
}
public function UserOptions() {
if ($cache = $this->cnn
->Cache('sqlsrv-engine')
->Get('UserOptions')) {
return $cache->data;
}
$data = Scheme\UserOptions::Get($this->cnn);
$this->cnn
->Cache('sqlsrv-engine')
->Set('UserOptions', $data);
return $data;
}
public function TableDetailsInvalidate($table) {
$this->cnn
->Cache('sqlsrv-tabledetails')
->Clear($table);
}
public function TableDetailsGet($table) {
if ($cache = $this->cnn
->Cache('sqlsrv-tabledetails')
->Get($table)) {
if (isset($cache->data['columns']) && !empty($cache->data['columns'])) {
return $cache->data;
}
}
if ($table[0] == '#') {
throw new \Exception('Temporary table introspection is not supported.');
}
$schema = $this
->GetDefaultSchema();
$info = [
'identity' => null,
'identities' => [],
'columns' => [],
'columns_clean' => [],
];
$result = $this->cnn
->query_execute("SELECT sysc.name, sysc.max_length, sysc.precision, sysc.collation_name,\n sysc.is_nullable, sysc.is_ansi_padded, sysc.is_identity, sysc.is_computed, TYPE_NAME(sysc.user_type_id) as type,\n syscc.definition,\n sm.[text] as default_value\n FROM sys.columns AS sysc\n INNER JOIN sys.syscolumns AS sysc2 ON sysc.object_id = sysc2.id and sysc.name = sysc2.name\n LEFT JOIN sys.computed_columns AS syscc ON sysc.object_id = syscc.object_id AND sysc.name = syscc.name\n LEFT JOIN sys.syscomments sm ON sm.id = sysc2.cdefault\n WHERE sysc.object_id = OBJECT_ID(:table)\n ", array(
':table' => $schema . '.' . $table,
));
foreach ($result as $column) {
if ($column->type == 'varbinary') {
$info['blobs'][$column->name] = true;
}
$column->sqlsrv_type = $column->type;
if ($this
->IsVariableLengthType($column->type)) {
if ($column->max_length == -1) {
$column->sqlsrv_type .= "(max)";
}
else {
$column->sqlsrv_type .= "({$column->max_length})";
}
}
$info['columns'][$column->name] = (array) $column;
if (!(isset($column->name[1]) && substr($column->name, 0, 2) == "__")) {
$info['columns_clean'][$column->name] = (array) $column;
}
if ($column->is_identity) {
$info['identities'][$column->name] = $column->name;
$info['identity'] = $column->name;
}
}
if (empty($info['columns']) && !$this
->TableExists($table)) {
throw new SchemaObjectDoesNotExistException("Table {$table} does not exist.", 25663);
}
$column_names = array_keys($info['columns']);
$column_regex = implode('|', $column_names);
foreach ($info['columns'] as &$column) {
$dependencies = array();
if (!empty($column['definition'])) {
$matches = array();
if (preg_match_all("/\\[[{$column_regex}\\]]*\\]/", $column['definition'], $matches) > 0) {
$dependencies = array_map(function ($m) {
return trim($m, "[]");
}, array_shift($matches));
}
}
$column['dependencies'] = array_flip($dependencies);
}
$result = $this->cnn
->query_execute("select tab.[name] as [table_name],\n idx.[name] as [index_name],\n allc.[name] as [column_name],\n idx.[type_desc],\n idx.[is_unique],\n idx.[data_space_id],\n idx.[ignore_dup_key],\n idx.[is_primary_key],\n idx.[is_unique_constraint],\n idx.[fill_factor],\n idx.[is_padded],\n idx.[is_disabled],\n idx.[is_hypothetical],\n idx.[allow_row_locks],\n idx.[allow_page_locks],\n idxc.[is_descending_key],\n idxc.[is_included_column],\n idxc.[index_column_id],\n idxc.[key_ordinal]\n FROM sys.[tables] as tab\n INNER join sys.[indexes] idx ON tab.[object_id] = idx.[object_id]\n INNER join sys.[index_columns] idxc ON idx.[object_id] = idxc.[object_id] and idx.[index_id] = idxc.[index_id]\n INNER join sys.[all_columns] allc ON tab.[object_id] = allc.[object_id] and idxc.[column_id] = allc.[column_id]\n WHERE tab.object_id = OBJECT_ID(:table)\n ORDER BY tab.[name], idx.[index_id], idxc.[index_column_id]\n ", array(
':table' => $schema . '.' . $table,
));
foreach ($result as $index_column) {
if (!isset($info['indexes'][$index_column->index_name])) {
$ic = clone $index_column;
unset($ic->column_name);
unset($ic->index_column_id);
unset($ic->is_descending_key);
unset($ic->table_name);
unset($ic->key_ordinal);
$info['indexes'][$index_column->index_name] = (array) $ic;
if ($index_column->is_primary_key) {
$info['primary_key_index'] = $ic->index_name;
}
}
$index =& $info['indexes'][$index_column->index_name];
$index['columns'][$index_column->key_ordinal] = array(
'name' => $index_column->column_name,
'is_descending_key' => $index_column->is_descending_key,
'key_ordinal' => $index_column->key_ordinal,
);
$info['columns'][$index_column->column_name]['indexes'][] = $index_column->index_name;
if (isset($info['columns_clean'][$index_column->column_name])) {
$info['columns_clean'][$index_column->column_name]['indexes'][] = $index_column->index_name;
}
}
$this->cnn
->Cache('sqlsrv-tabledetails')
->Set($table, $info);
return $info;
}
public function ColumnDetailsGet($table, $fields) {
$info = $this
->TableDetailsGet($table);
if (is_array($fields)) {
$result = array();
foreach ($fields as $field) {
$result[$field] = $info['columns'][$field];
}
return $result;
}
else {
return $info['columns'][$fields];
}
}
public function EngineVersion() {
if ($cache = $this->cnn
->Cache('sqlsrv-engine')
->Get('EngineVersion')) {
return $cache->data;
}
$version = Scheme\EngineVersion::Get($this->cnn);
$this->cnn
->Cache('sqlsrv-engine')
->Set('EngineVersion', $version);
return $version;
}
public function EngineVersionNumber() {
$version = $this
->EngineVersion()
->Version();
$parts = explode('.', $version);
return (int) reset($parts);
}
public function functionExists($function) {
return $this->cnn
->query_execute("SELECT 1 FROM sys.objects WHERE object_id = OBJECT_ID('" . $function . "') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT', N'AF')")
->fetchField() !== false;
}
public function CLREnabled() {
return $this->cnn
->query_execute("SELECT CONVERT(int, [value]) as [enabled] FROM sys.configurations WHERE name = 'clr enabled'")
->fetchField() !== 0;
}
private function IsVariableLengthType($type) {
$types = array(
'nvarchar' => true,
'ntext' => true,
'varchar' => true,
'varbinary' => true,
'image' => true,
);
return isset($types[$type]);
}
public function calculateClusteredIndexRowSizeBytes($table, $fields, $unique = true) {
$info = $this
->TableDetailsGet($table);
$num_cols = count($fields);
$num_variable_cols = 0;
$max_var_size = 0;
$max_fixed_size = 0;
foreach ($fields as $field) {
if ($this
->IsVariableLengthType($info['columns'][$field]['type'])) {
$num_variable_cols++;
$max_var_size += $info['columns'][$field]['max_length'];
}
else {
$max_fixed_size += $info['columns'][$field]['max_length'];
}
}
if (!$unique) {
$num_cols++;
$num_variable_cols++;
$max_var_size += 4;
}
$null_bitmap = 2 + ($num_cols + 7) / 8;
$variable_data_size = empty($num_variable_cols) ? 0 : 2 + $num_variable_cols * 2 + $max_var_size;
$row_size = $max_fixed_size + $variable_data_size + $null_bitmap + 4;
return $row_size;
}
public function DatabaseCreate($name, $collation = null) {
if ($collation !== null) {
$this->cnn
->query_execute("CREATE DATABASE {$name} COLLATE " . $collation);
}
else {
$this->cnn
->query_execute("CREATE DATABASE {$name}");
}
}
public function setRecoveryModel(RecoveryModel $model) {
$this->cnn
->query("ALTER {$this->cnn->options['name']} model SET RECOVERY {$model->__toString()}");
}
public function getSizeInfo($database) {
$sql = <<<EOF
SELECT
DB_NAME(db.database_id) DatabaseName,
(CAST(mfrows.RowSize AS FLOAT)*8)/1024 RowSizeMB,
(CAST(mflog.LogSize AS FLOAT)*8)/1024 LogSizeMB,
(CAST(mfstream.StreamSize AS FLOAT)*8)/1024 StreamSizeMB,
(CAST(mftext.TextIndexSize AS FLOAT)*8)/1024 TextIndexSizeMB
FROM sys.databases db
LEFT JOIN (SELECT database_id, SUM(size) RowSize FROM sys.master_files WHERE type = 0 GROUP BY database_id, type) mfrows ON mfrows.database_id = db.database_id
LEFT JOIN (SELECT database_id, SUM(size) LogSize FROM sys.master_files WHERE type = 1 GROUP BY database_id, type) mflog ON mflog.database_id = db.database_id
LEFT JOIN (SELECT database_id, SUM(size) StreamSize FROM sys.master_files WHERE type = 2 GROUP BY database_id, type) mfstream ON mfstream.database_id = db.database_id
LEFT JOIN (SELECT database_id, SUM(size) TextIndexSize FROM sys.master_files WHERE type = 4 GROUP BY database_id, type) mftext ON mftext.database_id = db.database_id
WHERE DB_NAME(db.database_id) = :database
EOF;
$result = null;
try {
$result = $this->cnn
->query_execute($sql, array(
':database' => $database,
))
->fetchObject();
} catch (\Exception $e) {
}
if (empty($result->RowSizeMB)) {
$sql = <<<EOF
SELECT (SUM(reserved_page_count) * 8192) / 1024 / 1024 AS DbSizeInMB
FROM sys.dm_db_partition_stats
EOF;
$result->RowSizeMB = $this->cnn
->query_execute($sql)
->fetchField();
}
$sql = <<<EOF
SELECT COUNT(*) from information_schema.tables
WHERE table_type = 'base table'
EOF;
$result->TableCount = $this->cnn
->query_execute($sql)
->fetchField();
return $result;
}
public function getDatabaseInfo($database) {
static $result;
if (isset($result)) {
return $result;
}
$sql = <<<EOF
select name
, db.snapshot_isolation_state
, db.snapshot_isolation_state_desc
, db.is_read_committed_snapshot_on
, db.recovery_model
, db.recovery_model_desc
, db.collation_name
from sys.databases db
WHERE DB_NAME(db.database_id) = :database
EOF;
$result = $this->cnn
->query_execute($sql, array(
':database' => $database,
))
->fetchObject();
return $result;
}
public function getCollation($database, $schema, $table = null, $column = null) {
if (empty($table) && empty($column)) {
if (!empty($database)) {
$sql = "SELECT CONVERT (varchar, DATABASEPROPERTYEX('{$database}', 'collation'))";
return $this->cnn
->query_execute($sql)
->fetchField();
}
else {
$sql = "SELECT SERVERPROPERTY ('collation') as collation";
return $this->cnn
->query_execute($sql)
->fetchField();
}
}
$sql = <<<EOF
SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, COLLATION_NAME, DATA_TYPE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = ':schema'
AND TABLE_NAME = ':table'
AND COLUMN_NAME = ':column'
EOF;
$params = array();
$params[':schema'] = $schema;
$params[':table'] = $table;
$params[':column'] = $column;
$result = $this->cnn
->query_execute($sql, $params)
->fetchObject();
return $result->COLLATION_NAME;
}
}