From 1c9bcf4427d6ed5ad46ff6b2665c82968c921b17 Mon Sep 17 00:00:00 2001
Message-Id: <1c9bcf4427d6ed5ad46ff6b2665c82968c921b17.1366399806.git.dmitriy.trt@gmail.com>
From: "Dmitriy.trt" <dmitriy.trt@gmail.com>
Date: Sat, 20 Apr 2013 02:26:34 +0700
Subject: [PATCH] Issue #1540608: Add incomplete SQL Server support.

Not yet implemented:
* Unique indexes.
* Indexes with specified limit on the column.
* No precision & scale inspection.
---
 engines/sqlsrv.inc |  234 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 schema.info        |    1 +
 2 files changed, 235 insertions(+)

diff --git a/engines/sqlsrv.inc b/engines/sqlsrv.inc
new file mode 100644
index 0000000..d77ba83
--- /dev/null
+++ b/engines/sqlsrv.inc
@@ -0,0 +1,234 @@
+<?php
+
+class SchemaDatabaseSchema_sqlsrv extends DatabaseSchema_sqlsrv {
+
+  /**
+   * Retrieve generated SQL to create a new table from a Drupal schema definition.
+   *
+   * @param $name
+   *   The name of the table to create.
+   * @param $table
+   *   A Schema API table definition array.
+   *
+   * @return
+   *   An array of SQL statements to create the table.
+   */
+  public function getCreateTableSql($name, $table) {
+    return parent::createTableSql($name, $table);
+  }
+
+  public function schema_type_map() {
+    static $map;
+    if (!isset($map)) {
+      $map = array_flip($this->getFieldTypeMap());
+    }
+    return $map;
+  }
+
+  public function inspect($connection = 'default', $tbl_name = NULL) {
+    $tables = array();
+
+    // Load columns list for all or specified table. Avoid "__pk" and
+    // "__unique_*" special columns. The former one is automatically generated
+    // for tables without primary key. And the latter one is a computed column
+    // for unique keys. We must explicitly set escape character.
+    // @see DatabaseSchema_sqlsrv::createTableSql()
+    // @see DatabaseSchema_sqlsrv::addUniqueKey()
+    // @see DatabaseConnection_sqlsrv::mapConditionOperator()
+    $sql = ('SELECT * FROM INFORMATION_SCHEMA.COLUMNS ' .
+            'WHERE COLUMN_NAME NOT LIKE :unique ESCAPE CHAR(92) AND COLUMN_NAME != :pk');
+    $args = array(
+      ':unique' => db_like('__unique_') . '%',
+      ':pk' => '__pk',
+    );
+    if (isset($tbl_name)) {
+      $sql .= ' AND TABLE_NAME = :name';
+      $args[':name'] = $tbl_name;
+    }
+    $sql .= ' ORDER BY TABLE_NAME';
+    $res = db_query($sql, $args);
+
+    // Iterate over columns and fill fields list of each table.
+    foreach ($res as $r) {
+      $table_name = $r->TABLE_NAME;
+      $column_name = $r->COLUMN_NAME;
+      $data_type = $r->DATA_TYPE;
+
+      if (!isset($tables[$table_name]['name'])) {
+        $unprefixed_table_name = schema_unprefix_table($table_name);
+        $tables[$table_name]['name'] = $unprefixed_table_name;
+      }
+
+      $col = array();
+
+      // Process max length.
+      if (isset($r->CHARACTER_MAXIMUM_LENGTH)) {
+        if ($r->CHARACTER_MAXIMUM_LENGTH == -1) {
+          // -1 means unlimited length, add suffix for proper schema type
+          // detection.
+          $data_type .= '(max)';
+        }
+        else {
+          $col['length'] = $r->CHARACTER_MAXIMUM_LENGTH;
+        }
+      }
+
+      // For float type add precision to the type for proper schema type
+      // detection.
+      if (strtolower($data_type) === 'float') {
+        $data_type .= '(' . $r->NUMERIC_PRECISION . ')';
+      }
+
+      // Convert DB type to the schema type and size.
+      list($col['type'], $col['size']) = schema_schema_type($data_type, $r->TABLE_NAME, $column_name, 'sqlsrv');
+
+      // Set sqlsrv_type property for some specific DB types, because otherwise
+      // they will be converted to other types on reverse engineering and
+      // re-creation.
+      // @todo: maybe some other types should be added to the list?
+      switch (strtolower($data_type)) {
+        case 'char':
+        case 'varchar':
+          $col['sqlsrv_type'] = $data_type;
+          break;
+
+        case 'float':
+          // "float(53)" is mapped to the "float:big", don't add database-
+          // specific type for it.
+          if ($r->NUMERIC_PRECISION != 53) {
+            $col['sqlsrv_type'] = $data_type . '(' . $r->NUMERIC_PRECISION . ')';
+          }
+          break;
+      }
+
+      if ($r->IS_NULLABLE === 'YES') {
+        $col['not null'] = FALSE;
+      }
+      else {
+        $col['not null'] = TRUE;
+      }
+
+      // Parse default value. Possible variants:
+      // ((99)) - numbers
+      // ('string') - strings
+      // (N'unicode') - unicode strings.
+      // @todo: make sure this method is reliable enough.
+      if (isset($r->COLUMN_DEFAULT)) {
+        $default = preg_replace(array(
+          '/^(?:\({2}|(?:\(N\\\')|(?:\(\\\'))/iu',
+          '/(?:\){2}|(\\\'\)))$/iu',
+        ), '', $r->COLUMN_DEFAULT);
+
+        // For numeric data types convert default to the number.
+        switch ($col['type']) {
+          case 'int':
+            $default = intval($default);
+            break;
+
+          case 'float':
+          case 'numeric':
+            $default = doubleval($default);
+            break;
+        }
+
+        $col['default'] = $default;
+      }
+
+      // @todo: Extract precision and scale.
+
+      $tables[$table_name]['fields'][$column_name] = $col;
+    }
+
+    if (empty($tables)) {
+      return $tables;
+    }
+
+    // Load CHECK constraints to fill "unsigned" parameter on fields.
+    $sql = ('SELECT cu.TABLE_NAME, cu.COLUMN_NAME, cc.CHECK_CLAUSE ' .
+            'FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE cu ' .
+            'INNER JOIN INFORMATION_SCHEMA.CHECK_CONSTRAINTS cc ON ' .
+              'cu.CONSTRAINT_CATALOG = cc.CONSTRAINT_CATALOG AND ' .
+              'cu.CONSTRAINT_SCHEMA = cc.CONSTRAINT_SCHEMA AND ' .
+              'cu.CONSTRAINT_NAME = cc.CONSTRAINT_NAME');
+    $args = array();
+    if (isset($tbl_name)) {
+      $sql .= ' WHERE cu.TABLE_NAME = :table_name';
+      $args[':table_name'] = $tbl_name;
+    }
+    $res = db_query($sql, $args);
+
+    // Fill "unsigned" property.
+    foreach ($res as $r) {
+      if (!isset($tables[$r->TABLE_NAME]['fields'][$r->COLUMN_NAME])) {
+        continue;
+      }
+
+      // Not sure if this method is reliable. Checked for SQL Server 2012.
+      if (preg_match('/^\(?\[?' . preg_quote($r->COLUMN_NAME) . '\]?\s*\>\=\s*\(?0\){0,2}$/i', $r->CHECK_CLAUSE)) {
+        $tables[$r->TABLE_NAME]['fields'][$r->COLUMN_NAME]['unsigned'] = TRUE;
+      }
+    }
+
+    // Load identity columns.
+    $sql = ('SELECT t.name as table_name, i.name as column_name ' .
+      'FROM sys.tables t ' .
+      'INNER JOIN sys.identity_columns i ON t.object_id = i.object_id ');
+    $args = array();
+    if (isset($tbl_name)) {
+      $sql .= 'AND t.name = :table_name ';
+      $args[':table_name'] = $tbl_name;
+    }
+    $sql .= 'ORDER BY t.name';
+    $res = db_query($sql, $args);
+
+    // Fill serial types.
+    foreach ($res as $r) {
+      if (!isset($tables[$r->table_name]['fields'][$r->column_name])) {
+        continue;
+      }
+
+      $tables[$r->table_name]['fields'][$r->column_name]['type'] = 'serial';
+    }
+
+    // Load indexes data. Because of the way unique indexes are created by
+    // sqlsrv database driver it's quite hard to get the list of columns in
+    // unique index. For now we load only primary keys or non-unique indexes.
+    // @todo: implement unique indexes support.
+    // @todo: implement limited length indexes support.
+    // @todo: make sure the columns order detection method is reliable.
+    $sql = ('SELECT t.name as table_name, i.is_primary_key, ' .
+              'i.name as index_name, c.name as column_name ' .
+            'FROM sys.tables t ' .
+            'INNER JOIN sys.indexes i ON t.object_id = i.object_id ' .
+            'INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id ' .
+              'AND i.index_id = ic.index_id ' .
+            'INNER JOIN sys.columns c ON ic.object_id = c.object_id ' .
+              'AND ic.column_id = c.column_id ' .
+            'WHERE (i.is_primary_key = 1 OR i.is_unique != 1) ');
+    $args = array();
+    if (isset($tbl_name)) {
+      $sql .= 'AND t.name = :table_name ';
+      $args[':table_name'] = $tbl_name;
+    }
+    $sql .= 'ORDER BY t.name, i.name, ic.is_included_column, ic.key_ordinal';
+    $res = db_query($sql, $args);
+
+    // Fill primary keys and non-unique indexes.
+    foreach ($res as $r) {
+      if (!isset($tables[$r->table_name]['fields'][$r->column_name])) {
+        continue;
+      }
+
+      if ($r->is_primary_key) {
+        $tables[$r->table_name]['primary key'][] = $r->column_name;
+      }
+      else {
+        $index_name = preg_replace('/_idx$/i', '', $r->index_name);
+        $tables[$r->table_name]['indexes'][$index_name][] = $r->column_name;
+      }
+    }
+
+    return $tables;
+  }
+
+}
\ No newline at end of file
diff --git a/schema.info b/schema.info
index 68c1951..bfe6c65 100755
--- a/schema.info
+++ b/schema.info
@@ -6,4 +6,5 @@ files[] = schema.module
 files[] = schema.install
 files[] = engines/mysql.inc
 files[] = engines/pgsql.inc
+files[] = engines/sqlsrv.inc
 files[] = tests/schema_regression.test
-- 
1.7.10.4

