<?php
/**
 * Zend Framework (http://framework.zend.com/)
 *
 * @link      http://github.com/zendframework/zf2 for the canonical source repository
 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
 * @license   http://framework.zend.com/license/new-bsd New BSD License
 */

namespace Zend\Db\Metadata\Source;

use 
Zend\Db\Adapter\Adapter;

class 
SqlServerMetadata extends AbstractSource
{


    protected function 
loadSchemaData()
    {
        if (isset(
$this->data['schemas'])) {
            return;
        }
        
$this->prepareDataHierarchy('schemas');

        
$p $this->adapter->getPlatform();

        
$sql 'SELECT ' $p->quoteIdentifier('SCHEMA_NAME')
            . 
' FROM ' $p->quoteIdentifierChain(array('INFORMATION_SCHEMA''SCHEMATA'))
            . 
' WHERE ' $p->quoteIdentifier('SCHEMA_NAME')
            . 
' != ' $p->quoteValue('INFORMATION_SCHEMA');

        
$results $this->adapter->query($sqlAdapter::QUERY_MODE_EXECUTE);

        
$schemas = array();
        foreach (
$results->toArray() as $row) {
            
$schemas[] = $row['SCHEMA_NAME'];
        }

        
$this->data['schemas'] = $schemas;
    }

    protected function 
loadTableNameData($schema)
    {
        if (isset(
$this->data['table_names'][$schema])) {
            return;
        }
        
$this->prepareDataHierarchy('table_names'$schema);

        
$p $this->adapter->getPlatform();

        
$isColumns = array(
            array(
'T','TABLE_NAME'),
            array(
'T','TABLE_TYPE'),
            array(
'V','VIEW_DEFINITION'),
            array(
'V','CHECK_OPTION'),
            array(
'V','IS_UPDATABLE'),
        );

        
array_walk($isColumns, function (&$c) use ($p) { $c $p->quoteIdentifierChain($c); });

        
$sql 'SELECT ' implode(', '$isColumns)
            . 
' FROM ' $p->quoteIdentifierChain(array('INFORMATION_SCHEMA''TABLES')) . ' t'

            
' LEFT JOIN ' $p->quoteIdentifierChain(array('INFORMATION_SCHEMA''VIEWS')) . ' v'
            
' ON ' $p->quoteIdentifierChain(array('T','TABLE_SCHEMA'))
            . 
'  = ' $p->quoteIdentifierChain(array('V','TABLE_SCHEMA'))
            . 
' AND ' $p->quoteIdentifierChain(array('T','TABLE_NAME'))
            . 
'  = ' $p->quoteIdentifierChain(array('V','TABLE_NAME'))

            . 
' WHERE ' $p->quoteIdentifierChain(array('T','TABLE_TYPE'))
            . 
' IN (' $p->quoteValueList(array('BASE TABLE''VIEW')) . ')';

        if (
$schema != self::DEFAULT_SCHEMA) {
            
$sql .= ' AND ' $p->quoteIdentifierChain(array('T','TABLE_SCHEMA'))
                . 
' = ' $p->quoteValue($schema);
        } else {
            
$sql .= ' AND ' $p->quoteIdentifierChain(array('T','TABLE_SCHEMA'))
                . 
' != ' $p->quoteValue('INFORMATION_SCHEMA');
        }

        
$results $this->adapter->query($sqlAdapter::QUERY_MODE_EXECUTE);

        
$tables = array();
        foreach (
$results->toArray() as $row) {
            
$tables[$row['TABLE_NAME']] = array(
                
'table_type' => $row['TABLE_TYPE'],
                
'view_definition' => $row['VIEW_DEFINITION'],
                
'check_option' => $row['CHECK_OPTION'],
                
'is_updatable' => ('YES' == $row['IS_UPDATABLE']),
            );
        }

        
$this->data['table_names'][$schema] = $tables;
    }

    protected function 
loadColumnData($table$schema)
    {
        if (isset(
$this->data['columns'][$schema][$table])) {
            return;
        }
        
$this->prepareDataHierarchy('columns'$schema$table);
        
$p $this->adapter->getPlatform();

        
$isColumns = array(
            array(
'C','ORDINAL_POSITION'),
            array(
'C','COLUMN_DEFAULT'),
            array(
'C','IS_NULLABLE'),
            array(
'C','DATA_TYPE'),
            array(
'C','CHARACTER_MAXIMUM_LENGTH'),
            array(
'C','CHARACTER_OCTET_LENGTH'),
            array(
'C','NUMERIC_PRECISION'),
            array(
'C','NUMERIC_SCALE'),
            array(
'C','COLUMN_NAME'),
        );

        
array_walk($isColumns, function (&$c) use ($p) { $c $p->quoteIdentifierChain($c); });

        
$sql 'SELECT ' implode(', '$isColumns)
            . 
' FROM ' $p->quoteIdentifierChain(array('INFORMATION_SCHEMA','TABLES')) . 'T'
            
' INNER JOIN ' $p->quoteIdentifierChain(array('INFORMATION_SCHEMA','COLUMNS')) . 'C'
            
' ON ' $p->quoteIdentifierChain(array('T','TABLE_SCHEMA'))
            . 
'  = ' $p->quoteIdentifierChain(array('C','TABLE_SCHEMA'))
            . 
' AND ' $p->quoteIdentifierChain(array('T','TABLE_NAME'))
            . 
'  = ' $p->quoteIdentifierChain(array('C','TABLE_NAME'))
            . 
' WHERE ' $p->quoteIdentifierChain(array('T','TABLE_TYPE'))
            . 
' IN (' $p->quoteValueList(array('BASE TABLE''VIEW')) . ')'
            
' AND ' $p->quoteIdentifierChain(array('T','TABLE_NAME'))
            . 
'  = ' $p->quoteValue($table);

        if (
$schema != self::DEFAULT_SCHEMA) {
            
$sql .= ' AND ' $p->quoteIdentifierChain(array('T','TABLE_SCHEMA'))
                . 
' = ' $p->quoteValue($schema);
        } else {
            
$sql .= ' AND ' $p->quoteIdentifierChain(array('T','TABLE_SCHEMA'))
                . 
' != ' $p->quoteValue('INFORMATION_SCHEMA');
        }

        
$results $this->adapter->query($sqlAdapter::QUERY_MODE_EXECUTE);
        
$columns = array();
        foreach (
$results->toArray() as $row) {
            
$columns[$row['COLUMN_NAME']] = array(
                
'ordinal_position'          => $row['ORDINAL_POSITION'],
                
'column_default'            => $row['COLUMN_DEFAULT'],
                
'is_nullable'               => ('YES' == $row['IS_NULLABLE']),
                
'data_type'                 => $row['DATA_TYPE'],
                
'character_maximum_length'  => $row['CHARACTER_MAXIMUM_LENGTH'],
                
'character_octet_length'    => $row['CHARACTER_OCTET_LENGTH'],
                
'numeric_precision'         => $row['NUMERIC_PRECISION'],
                
'numeric_scale'             => $row['NUMERIC_SCALE'],
                
'numeric_unsigned'          => null,
                
'erratas'                   => array(),
            );
        }

        
$this->data['columns'][$schema][$table] = $columns;
    }

    protected function 
loadConstraintData($table$schema)
    {
        if (isset(
$this->data['constraints'][$schema][$table])) {
            return;
        }

        
$this->prepareDataHierarchy('constraints'$schema$table);

        
$isColumns = array(
            array(
'T','TABLE_NAME'),
            array(
'TC','CONSTRAINT_NAME'),
            array(
'TC','CONSTRAINT_TYPE'),
            array(
'KCU','COLUMN_NAME'),
            array(
'CC','CHECK_CLAUSE'),
            array(
'RC','MATCH_OPTION'),
            array(
'RC','UPDATE_RULE'),
            array(
'RC','DELETE_RULE'),
            array(
'REFERENCED_TABLE_SCHEMA' => 'KCU2','TABLE_SCHEMA'),
            array(
'REFERENCED_TABLE_NAME' => 'KCU2','TABLE_NAME'),
            array(
'REFERENCED_COLUMN_NAME' => 'KCU2','COLUMN_NAME'),
        );

        
$p $this->adapter->getPlatform();

        
array_walk($isColumns, function (&$c) use ($p) {
            
$alias key($c);
            
$c $p->quoteIdentifierChain($c);
            if (
is_string($alias)) {
                
$c .= ' ' $p->quoteIdentifier($alias);
            }
        });

        
$sql 'SELECT ' implode(', '$isColumns)
             . 
' FROM ' $p->quoteIdentifierChain(array('INFORMATION_SCHEMA','TABLES')) . ' T'

             
' INNER JOIN ' $p->quoteIdentifierChain(array('INFORMATION_SCHEMA','TABLE_CONSTRAINTS')) . ' TC'
             
' ON ' $p->quoteIdentifierChain(array('T','TABLE_SCHEMA'))
             . 
'  = ' $p->quoteIdentifierChain(array('TC','TABLE_SCHEMA'))
             . 
' AND ' $p->quoteIdentifierChain(array('T','TABLE_NAME'))
             . 
'  = ' $p->quoteIdentifierChain(array('TC','TABLE_NAME'))

             . 
' LEFT JOIN ' $p->quoteIdentifierChain(array('INFORMATION_SCHEMA','KEY_COLUMN_USAGE')) . ' KCU'
             
' ON ' $p->quoteIdentifierChain(array('TC','TABLE_SCHEMA'))
             . 
'  = ' $p->quoteIdentifierChain(array('KCU','TABLE_SCHEMA'))
             . 
' AND ' $p->quoteIdentifierChain(array('TC','TABLE_NAME'))
             . 
'  = ' $p->quoteIdentifierChain(array('KCU','TABLE_NAME'))
             . 
' AND ' $p->quoteIdentifierChain(array('TC','CONSTRAINT_NAME'))
             . 
'  = ' $p->quoteIdentifierChain(array('KCU','CONSTRAINT_NAME'))

             . 
' LEFT JOIN ' $p->quoteIdentifierChain(array('INFORMATION_SCHEMA','CHECK_CONSTRAINTS')) . ' CC'
             
' ON ' $p->quoteIdentifierChain(array('TC','CONSTRAINT_SCHEMA'))
             . 
'  = ' $p->quoteIdentifierChain(array('CC','CONSTRAINT_SCHEMA'))
             . 
' AND ' $p->quoteIdentifierChain(array('TC','CONSTRAINT_NAME'))
             . 
'  = ' $p->quoteIdentifierChain(array('CC','CONSTRAINT_NAME'))

             . 
' LEFT JOIN ' $p->quoteIdentifierChain(array('INFORMATION_SCHEMA','REFERENTIAL_CONSTRAINTS')) . ' RC'
             
' ON ' $p->quoteIdentifierChain(array('TC','CONSTRAINT_SCHEMA'))
             . 
'  = ' $p->quoteIdentifierChain(array('RC','CONSTRAINT_SCHEMA'))
             . 
' AND ' $p->quoteIdentifierChain(array('TC','CONSTRAINT_NAME'))
             . 
'  = ' $p->quoteIdentifierChain(array('RC','CONSTRAINT_NAME'))

             . 
' LEFT JOIN ' $p->quoteIdentifierChain(array('INFORMATION_SCHEMA','KEY_COLUMN_USAGE')) . ' KCU2'
             
' ON ' $p->quoteIdentifierChain(array('RC','UNIQUE_CONSTRAINT_SCHEMA'))
             . 
'  = ' $p->quoteIdentifierChain(array('KCU2','CONSTRAINT_SCHEMA'))
             . 
' AND ' $p->quoteIdentifierChain(array('RC','UNIQUE_CONSTRAINT_NAME'))
             . 
'  = ' $p->quoteIdentifierChain(array('KCU2','CONSTRAINT_NAME'))
             . 
' AND ' $p->quoteIdentifierChain(array('KCU','ORDINAL_POSITION'))
             . 
'  = ' $p->quoteIdentifierChain(array('KCU2','ORDINAL_POSITION'))

             . 
' WHERE ' $p->quoteIdentifierChain(array('T','TABLE_NAME'))
             . 
' = ' $p->quoteValue($table)
             . 
' AND ' $p->quoteIdentifierChain(array('T','TABLE_TYPE'))
             . 
' IN (' $p->quoteValueList(array('BASE TABLE''VIEW')) . ')';

        if (
$schema != self::DEFAULT_SCHEMA) {
            
$sql .= ' AND ' $p->quoteIdentifierChain(array('T','TABLE_SCHEMA'))
            . 
' = ' $p->quoteValue($schema);
        } else {
            
$sql .= ' AND ' $p->quoteIdentifierChain(array('T','TABLE_SCHEMA'))
            . 
' != ' $p->quoteValue('INFORMATION_SCHEMA');
        }

        
$sql .= ' ORDER BY CASE ' $p->quoteIdentifierChain(array('TC','CONSTRAINT_TYPE'))
              . 
" WHEN 'PRIMARY KEY' THEN 1"
              
" WHEN 'UNIQUE' THEN 2"
              
" WHEN 'FOREIGN KEY' THEN 3"
              
" WHEN 'CHECK' THEN 4"
              
" ELSE 5 END"
              
', ' $p->quoteIdentifierChain(array('TC','CONSTRAINT_NAME'))
              . 
', ' $p->quoteIdentifierChain(array('KCU','ORDINAL_POSITION'));

        
$results $this->adapter->query($sqlAdapter::QUERY_MODE_EXECUTE);

        
$name null;
        
$constraints = array();
        
$isFK false;
        foreach (
$results->toArray() as $row) {
            if (
$row['CONSTRAINT_NAME'] !== $name) {
                
$name $row['CONSTRAINT_NAME'];
                
$constraints[$name] = array(
                    
'constraint_name' => $name,
                    
'constraint_type' => $row['CONSTRAINT_TYPE'],
                    
'table_name'      => $row['TABLE_NAME'],
                );
                if (
'CHECK' == $row['CONSTRAINT_TYPE']) {
                    
$constraints[$name]['check_clause'] = $row['CHECK_CLAUSE'];
                    continue;
                }
                
$constraints[$name]['columns'] = array();
                
$isFK = ('FOREIGN KEY' == $row['CONSTRAINT_TYPE']);
                if (
$isFK) {
                    
$constraints[$name]['referenced_table_schema'] = $row['REFERENCED_TABLE_SCHEMA'];
                    
$constraints[$name]['referenced_table_name']   = $row['REFERENCED_TABLE_NAME'];
                    
$constraints[$name]['referenced_columns']      = array();
                    
$constraints[$name]['match_option']       = $row['MATCH_OPTION'];
                    
$constraints[$name]['update_rule']        = $row['UPDATE_RULE'];
                    
$constraints[$name]['delete_rule']        = $row['DELETE_RULE'];
                }
            }
            
$constraints[$name]['columns'][] = $row['COLUMN_NAME'];
            if (
$isFK) {
                
$constraints[$name]['referenced_columns'][] = $row['REFERENCED_COLUMN_NAME'];
            }
        }

        
$this->data['constraints'][$schema][$table] = $constraints;
    }

    protected function 
loadTriggerData($schema)
    {
        if (isset(
$this->data['triggers'][$schema])) {
            return;
        }

        
$this->prepareDataHierarchy('triggers'$schema);

        
$p $this->adapter->getPlatform();

        
$isColumns = array(
            
'TRIGGER_NAME',
            
'EVENT_MANIPULATION',
            
'EVENT_OBJECT_CATALOG',
            
'EVENT_OBJECT_SCHEMA',
            
'EVENT_OBJECT_TABLE',
            
'ACTION_ORDER',
            
'ACTION_CONDITION',
            
'ACTION_STATEMENT',
            
'ACTION_ORIENTATION',
            
'ACTION_TIMING',
            
'ACTION_REFERENCE_OLD_TABLE',
            
'ACTION_REFERENCE_NEW_TABLE',
            
'ACTION_REFERENCE_OLD_ROW',
            
'ACTION_REFERENCE_NEW_ROW',
            
'CREATED',
        );

        
array_walk($isColumns, function (&$c) use ($p) {
            
$c $p->quoteIdentifier($c);
        });

        
$sql 'SELECT ' implode(', '$isColumns)
            . 
' FROM ' $p->quoteIdentifierChain(array('INFORMATION_SCHEMA','TRIGGERS'))
            . 
' WHERE ';

        if (
$schema != self::DEFAULT_SCHEMA) {
            
$sql .= $p->quoteIdentifier('TRIGGER_SCHEMA')
                . 
' = ' $p->quoteValue($schema);
        } else {
            
$sql .= $p->quoteIdentifier('TRIGGER_SCHEMA')
                . 
' != ' $p->quoteValue('INFORMATION_SCHEMA');
        }

        
$results $this->adapter->query($sqlAdapter::QUERY_MODE_EXECUTE);

        
$data = array();
        foreach (
$results->toArray() as $row) {
            
$row array_change_key_case($rowCASE_LOWER);
            if (
null !== $row['created']) {
                
$row['created'] = new \DateTime($row['created']);
            }
            
$data[$row['trigger_name']] = $row;
        }

        
$this->data['triggers'][$schema] = $data;
    }
}