MSSQLPDODatabase::getIndexes()   A
last analyzed

Complexity

Conditions 5
Paths 9

Size

Total Lines 47
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 18
c 1
b 0
f 0
nc 9
nop 2
dl 0
loc 47
ccs 0
cts 19
cp 0
crap 30
rs 9.3554
1
<?php
2
3
declare(strict_types=1);
4
/**
5
 * Database Access Layer.
6
 * @author Doug Wright
7
 */
8
9
namespace DVDoug\DB;
10
11
use function substr;
12
13
/**
14
 * MSSQL database connection (PDO).
15
 * @author Doug Wright
16
 */
17
class MSSQLPDODatabase extends PDODatabase
18
{
19
    /**
20
     * Character to use when quoting identifiers.
21
     */
22
    public const IDENTIFIER_OPENQUOTE = '"';
23
24
    /**
25
     * Character to use when quoting identifiers.
26
     */
27
    public const IDENTIFIER_CLOSEQUOTE = '"';
28
29
    /**
30
     * Constructor.
31
     * @param string $aHost            hostname to connect to
32
     * @param int    $aPort            port number to connect to
33
     * @param string $aDefaultDatabase name of default database to use
34
     * @param string $aUsername        connection username
35
     * @param string $aPassword        connection password
36
     */
37
    public function __construct($aHost, $aPort, $aDefaultDatabase, $aUsername, $aPassword)
38
    {
39
        parent::__construct("sqlsrv:Server={$aHost},{$aPort};Database={$aDefaultDatabase}", $aUsername, $aPassword);
40
    }
41
42
    /**
43
     * Escapes/quotes a parameter for use in a query.
44
     * @param  mixed  $aParam     the parameter to be quoted
45
     * @param  int    $aParamType data type hint for drivers
46
     * @return string a quoted string that is theoretically safe to pass into an SQL statement
47
     */
48
    public function escape($aParam, $aParamType = DatabaseInterface::PARAM_IS_STR)
49
    {
50
        switch ($aParamType) {
51
            default:
52
                return parent::escape($aParam, $aParamType);
53
        }
54
    }
55
56
    /**
57
     * List of tables in a database.
58
     * @param string $aDatabase database/schema name
59
     */
60
    public function getTables($aDatabase = null): array
61
    {
62
        if ($aDatabase) {
63
            $statement = $this->prepare('SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = :database ORDER BY TABLE_NAME ASC');
64
            $statement->bindParamToValue(':database', $aDatabase);
0 ignored issues
show
Bug introduced by
The method bindParamToValue() does not exist on PDOStatement. Did you maybe mean bindParam()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

64
            $statement->/** @scrutinizer ignore-call */ 
65
                        bindParamToValue(':database', $aDatabase);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
65
            $statement->execute();
66
        } else {
67
            $statement = $this->query('SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES');
68
        }
69
70
        $result = $statement->fetchAssoc(true, true);
71
72
        $tables = [];
73
        foreach ($result as $database => $dbtables) {
74
            $tables[$database] = [];
75
            foreach ($dbtables as $table) {
76
                $tables[$database][] = $table['TABLE_NAME'];
77
            }
78
        }
79
80
        return $aDatabase ? $tables[$aDatabase] : $tables;
81
    }
82
83
    /**
84
     * List of columns (and types) in a table.
85
     * @param  string                $aDatabase database/schema name
86
     * @param  string                $aTable    table name
87
     * @return ColumnMetaInterface[]
88
     */
89
    public function getTableColumns($aDatabase, $aTable): array
90
    {
91
        $statement = $this->prepare('SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = :database AND TABLE_NAME = :table_name ORDER BY ORDINAL_POSITION ASC');
92
        $statement->bindParamToValue(':database', $aDatabase);
93
        $statement->bindParamToValue(':table_name', $aTable);
94
        $statement->execute();
95
96
        $result = $statement->fetchAssoc();
0 ignored issues
show
Bug introduced by
The method fetchAssoc() does not exist on PDOStatement. Did you maybe mean fetch()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

96
        /** @scrutinizer ignore-call */ 
97
        $result = $statement->fetchAssoc();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
97
        $columns = [];
98
        foreach ($result as $row) {
99
            $columns[$row['COLUMN_NAME']] = new MSSQLColumnMeta($this, $aDatabase, $aTable, $row['COLUMN_NAME']);
100
        }
101
102
        return $columns;
103
    }
104
105
    /**
106
     * Primary key column(s).
107
     * @param string $aDatabase database/schema name
108
     * @param string $aTable    table name
109
     */
110
    public function getPrimaryKey($aDatabase, $aTable): array
111
    {
112
        $columns = [];
113
        $SQL = 'SELECT ind.name AS INDEX_NAME,
114
                     col.name AS COLUMN_NAME
115
              FROM sys.indexes ind
116
                   JOIN sys.index_columns ic
117
                     ON ind.object_id = ic.object_id
118
                        AND ind.index_id = ic.index_id
119
                   JOIN sys.columns col
120
                     ON ic.object_id = col.object_id
121
                        AND ic.column_id = col.column_id
122
                   JOIN sys.tables t
123
                     ON ind.object_id = t.object_id
124
                   JOIN sys.schemas s
125
                     ON t.schema_id = s.schema_id
126
             WHERE ind.is_primary_key = 1
127
                   AND col.is_nullable = 0
128
                   AND s.name = :database
129
                   AND t.name = :table_name
130
             ORDER BY ind.name, ic.index_column_id';
131
        $statement = $this->prepare($SQL);
132
        $statement->bindParamToValue(':database', $aDatabase);
133
        $statement->bindParamToValue(':table_name', $aTable);
134
        $statement->execute();
135
136
        $result = $statement->fetchAssoc();
137
        foreach ($result as $column) {
138
            $columns[] = $column['COLUMN_NAME'];
139
        }
140
141
        if (!$columns) { //Try uniqueidentifier
142
            $statement = $this->prepare("SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE DATA_TYPE = 'uniqueidentifier' AND TABLE_SCHEMA = :database AND TABLE_NAME = :table_name");
143
            $statement->bindParamToValue(':database', $aDatabase);
144
            $statement->bindParamToValue(':table_name', $aTable);
145
            $statement->execute();
146
            $result = $statement->fetchAssoc(false);
147
            if ($result) {
148
                $columns[] = $result['COLUMN_NAME'];
149
            }
150
        }
151
152
        return $columns;
153
    }
154
155
    /**
156
     * Non-PK indexes.
157
     * @param string $aDatabase database/schema name
158
     * @param string $aTable    table name
159
     */
160
    public function getIndexes($aDatabase, $aTable): array
161
    {
162
        $indexes = [];
163
        $SQL = 'SELECT ind.name AS INDEX_NAME,
164
                     col.name AS COLUMN_NAME
165
              FROM sys.indexes ind
166
                   JOIN sys.index_columns ic
167
                     ON ind.object_id = ic.object_id
168
                        AND ind.index_id = ic.index_id
169
                   JOIN sys.columns col
170
                     ON ic.object_id = col.object_id
171
                        AND ic.column_id = col.column_id
172
                   JOIN sys.tables t
173
                     ON ind.object_id = t.object_id
174
                   JOIN sys.schemas s
175
                     ON t.schema_id = s.schema_id
176
             WHERE ind.is_primary_key = 0
177
                   AND s.name = :database
178
                   AND t.name = :table_name
179
             ORDER BY ind.name ASC, ic.index_column_id ASC';
180
        $statement = $this->prepare($SQL);
181
        $statement->bindParamToValue(':database', $aDatabase);
182
        $statement->bindParamToValue(':table_name', $aTable);
183
        $statement->execute();
184
185
        $result = $statement->fetchAssoc(true, true);
186
187
        foreach ($result as $index => $columnList) {
188
            $index = substr($index, 0, 64);
189
            $indexes[$index] = [];
190
            foreach ($columnList as $col) {
191
                $indexes[$index][] = $col['COLUMN_NAME'];
192
            }
193
        }
194
195
        /*
196
         * Subtract PK if any
197
         */
198
        $PK = $this->getPrimaryKey($aDatabase, $aTable);
199
        foreach ($indexes as $name => $columns) {
200
            if ($PK === $columns) {
201
                unset($indexes[$name]);
202
                break;
203
            }
204
        }
205
206
        return $indexes;
207
    }
208
}
209