Passed
Push — master ( f71b04...7dcb0c )
by P.R.
12:30
created

MySqlMetadataLayer::allTablesNames()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 11
rs 10
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace SetBased\Stratum\MySql;
5
6
use SetBased\Stratum\Backend\StratumStyle;
7
use SetBased\Stratum\Middle\Exception\ResultException;
8
use SetBased\Stratum\MySql\Exception\MySqlQueryErrorException;
9
10
/**
11
 * Data layer for retrieving metadata and loading stored routines.
12
 */
13
class MySqlMetadataLayer
14
{
15
  //--------------------------------------------------------------------------------------------------------------------
16
  /**
17
   * The connection to the MySQL instance.
18
   *
19
   * @var MySqlDataLayer|null
20
   */
21
  private ?MySqlDataLayer $dl;
22
23
  /**
24
   * The Output decorator.
25
   *
26
   * @var StratumStyle
27
   */
28
  private StratumStyle $io;
29
30
  //--------------------------------------------------------------------------------------------------------------------
31
  /**
32
   * MySqlMetadataLayer constructor.
33
   *
34
   * @param MySqlDataLayer $dl The connection to the MySQL instance.
35
   * @param StratumStyle   $io The Output decorator.
36
   */
37 1
  public function __construct(MySqlDataLayer $dl, StratumStyle $io)
38
  {
39 1
    $this->dl = $dl;
40 1
    $this->io = $io;
41
  }
42
43
  //--------------------------------------------------------------------------------------------------------------------
44
  /**
45
   * Selects the details of all character sets.
46
   *
47
   * @return array[]
48
   *
49
   * @throws MySqlQueryErrorException
50
   */
51 1
  public function allCharacterSets(): array
52
  {
53 1
    $sql = "
54
select CHARACTER_SET_NAME  as  character_set_name
55
,      MAXLEN              as  maxlen
56
from   information_schema.CHARACTER_SETS
57
order by CHARACTER_SET_NAME";
58
59 1
    return $this->executeRows($sql);
60
  }
61
62
  //--------------------------------------------------------------------------------------------------------------------
63
  /**
64
   * Selects metadata of tables with a label column.
65
   *
66
   * @return array[]
67
   *
68
   * @throws MySqlQueryErrorException
69
   */
70 1
  public function allLabelTables(): array
71
  {
72 1
    $sql = "
73
select t1.TABLE_NAME   as  table_name
74
,      t1.COLUMN_NAME  as  id
75
,      t2.COLUMN_NAME  as  label
76
from       information_schema.COLUMNS t1
77
inner join information_schema.COLUMNS t2 on t1.table_name = t2.table_name
78
where t1.table_schema = database()
79
and   t1.extra        = 'auto_increment'
80
and   t2.table_schema = database()
81
and   t2.column_name like '%%\\_label'";
82
83 1
    return $this->executeRows($sql);
84
  }
85
86
  //--------------------------------------------------------------------------------------------------------------------
87
  /**
88
   * Selects all routines in the current schema.
89
   *
90
   * @return array[]
91
   *
92
   * @throws MySqlQueryErrorException
93
   */
94 1
  public function allRoutines(): array
95
  {
96 1
    $sql = "
97
select ROUTINE_NAME          as  routine_name                            
98
,      ROUTINE_TYPE          as  routine_type           
99
,      SQL_MODE              as  sql_mode       
100
,      CHARACTER_SET_CLIENT  as  character_set_client                   
101
,      COLLATION_CONNECTION  as  collation_connection                   
102
from  information_schema.ROUTINES
103
where ROUTINE_SCHEMA = database()
104
and   ROUTINE_TYPE in ('PROCEDURE', 'FUNCTION')
105
order by routine_name";
106
107 1
    return $this->executeRows($sql);
108
  }
109
110
  //--------------------------------------------------------------------------------------------------------------------
111
  /**
112
   * Selects metadata of all columns of all tables.
113
   *
114
   * @return array[]
115
   *
116
   * @throws MySqlQueryErrorException
117
   */
118 1
  public function allTableColumns(): array
119
  {
120 1
    $sql = "
121
(
122
  select TABLE_NAME                as  table_name                                                       
123
  ,      COLUMN_NAME               as  column_name                                 
124
  ,      COLUMN_TYPE               as  column_type                                 
125
  ,      DATA_TYPE                 as  data_type                                 
126
  ,      CHARACTER_MAXIMUM_LENGTH  as  character_maximum_length                                 
127
  ,      CHARACTER_SET_NAME        as  character_set_name                                 
128
  ,      COLLATION_NAME            as  collation_name                                 
129
  ,      NUMERIC_PRECISION         as  numeric_precision                                 
130
  ,      NUMERIC_SCALE             as  numeric_scale                                 
131
  from   information_schema.COLUMNS
132
  where  TABLE_SCHEMA = database()
133
  and    TABLE_NAME  rlike '^[a-zA-Z0-9_]*$'
134
  and    COLUMN_NAME rlike '^[a-zA-Z0-9_]*$'
135
  order by TABLE_NAME
136
  ,        ORDINAL_POSITION
137
)
138
139
union all
140
141
(
142
  select concat(TABLE_SCHEMA,'.',TABLE_NAME)  as  table_name                                               
143
  ,      COLUMN_NAME                          as  column_name                     
144
  ,      COLUMN_TYPE                          as  column_type                     
145
  ,      DATA_TYPE                            as  data_type                     
146
  ,      CHARACTER_MAXIMUM_LENGTH             as  character_maximum_length                     
147
  ,      CHARACTER_SET_NAME                   as  character_set_name                     
148
  ,      COLLATION_NAME                       as  collation_name                     
149
  ,      NUMERIC_PRECISION                    as  numeric_precision                     
150
  ,      NUMERIC_SCALE                        as  numeric_scale                     
151
  from   information_schema.COLUMNS
152
  where  TABLE_NAME  rlike '^[a-zA-Z0-9_]*$'
153
  and    COLUMN_NAME rlike '^[a-zA-Z0-9_]*$'
154
  order by TABLE_SCHEMA
155
  ,        TABLE_NAME
156
  ,        ORDINAL_POSITION
157
)
158
";
159
160 1
    return $this->executeRows($sql);
161
  }
162
163
  //--------------------------------------------------------------------------------------------------------------------
164
  /**
165
   * Selects all table names in a schema.
166
   *
167
   * @param string $schemaName The name of the schema.
168
   *
169
   * @return array[]
170
   *
171
   * @throws MySqlQueryErrorException
172
   */
173
  public function allTablesNames(string $schemaName): array
174
  {
175
    $sql = sprintf("
176
select TABLE_NAME  as  table_name
177
from   information_schema.TABLES
178
where  TABLE_SCHEMA = %s
179
and    TABLE_TYPE   = 'BASE TABLE'
180
order by TABLE_NAME",
181
                   $this->dl->quoteString($schemaName));
0 ignored issues
show
Bug introduced by
The method quoteString() does not exist on null. ( Ignorable by Annotation )

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

181
                   $this->dl->/** @scrutinizer ignore-call */ 
182
                              quoteString($schemaName));

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...
182
183
    return $this->executeRows($sql);
184
  }
185
186
  //--------------------------------------------------------------------------------------------------------------------
187
  /**
188
   * Class a stored procedure without arguments.
189
   *
190
   * @param string $procedureName The name of the procedure.
191
   *
192
   * @throws MySqlQueryErrorException
193 1
   */
194
  public function callProcedure(string $procedureName): void
195 1
  {
196
    $sql = sprintf('call %s()', $procedureName);
197 1
198
    $this->executeNone($sql);
199
  }
200
201
  //--------------------------------------------------------------------------------------------------------------------
202
  /**
203
   * Checks if a table exists in the current schema.
204
   *
205
   * @param string $tableName The name of the table.
206
   *
207
   * @return bool
208
   *
209
   * @throws MySqlQueryErrorException
210
   * @throws ResultException
211 1
   */
212
  public function checkTableExists(string $tableName): bool
213 1
  {
214
    $sql = sprintf('
215
select 1
216
from   information_schema.TABLES
217 1
where table_schema = database()
218
and   table_name   = %s',
219 1
                   $this->dl->quoteString($tableName));
220
221
    return !empty($this->executeSingleton0($sql));
222
  }
223
224
  //--------------------------------------------------------------------------------------------------------------------
225
  /**
226
   * Describes a table.
227
   *
228
   * @param string $tableName The table name.
229
   *
230
   * @return array[]
231
   *
232 1
   * @throws MySqlQueryErrorException
233
   */
234 1
  public function describeTable(string $tableName): array
235
  {
236 1
    $sql = sprintf('describe `%s`', $tableName);
237
238
    return $this->executeRows($sql);
239
  }
240
241
  //--------------------------------------------------------------------------------------------------------------------
242
  /**
243 1
   * Closes the connection to the MySQL instance, if connected.
244
   */
245 1
  public function disconnect(): void
246
  {
247 1
    if ($this->dl!==null)
248 1
    {
249
      $this->dl->disconnect();
250
      $this->dl = null;
251
    }
252
  }
253
254
  //--------------------------------------------------------------------------------------------------------------------
255
  /**
256
   * Drops a routine if it exists.
257
   *
258
   * @param string $routineType The type of the routine (function of procedure).
259
   * @param string $routineName The name of the routine.
260
   *
261
   * @throws MySqlQueryErrorException
262
   */
263
  public function dropRoutine(string $routineType, string $routineName): void
264
  {
265
    $sql = sprintf('drop %s if exists `%s`', $routineType, $routineName);
266
267
    $this->executeNone($sql);
268
  }
269
270
  //--------------------------------------------------------------------------------------------------------------------
271
  /**
272
   * Drops a temporary table.
273
   *
274
   * @param string $tableName the name of the temporary table.
275
   *
276 1
   * @throws MySqlQueryErrorException
277
   */
278 1
  public function dropTemporaryTable(string $tableName): void
279
  {
280 1
    $sql = sprintf('drop temporary table `%s`', $tableName);
281
282
    $this->executeNone($sql);
283
  }
284
285
  //--------------------------------------------------------------------------------------------------------------------
286
  /**
287
   * @param string $sql The SQL statement.
288
   *
289
   * @return int The number of affected rows (if any).
290
   *
291 1
   * @throws MySqlQueryErrorException
292
   */
293 1
  public function executeNone(string $sql): int
294
  {
295 1
    $this->logQuery($sql);
296
297
    return $this->dl->executeNone($sql);
298
  }
299
300
  //--------------------------------------------------------------------------------------------------------------------
301
  /**
302
   * Executes a query that returns 0 or 1 row.
303
   * Throws an exception if the query selects 2 or more rows.
304
   *
305
   * @param string $sql The SQL statement.
306
   *
307
   * @return array|null The selected row.
308
   *
309
   * @throws MySqlQueryErrorException
310
   * @throws ResultException
311
   */
312
  public function executeRow0(string $sql): ?array
313
  {
314
    $this->logQuery($sql);
315
316
    return $this->dl->executeRow0($sql);
317
  }
318
319
  //--------------------------------------------------------------------------------------------------------------------
320
  /**
321
   * Executes a query that returns 1 and only 1 row.
322
   * Throws an exception if the query selects none, 2 or more rows.
323
   *
324
   * @param string $sql The SQL statement.
325
   *
326
   * @return array The selected row.
327
   *
328
   * @throws MySqlQueryErrorException
329
   * @throws ResultException
330
   */
331
  public function executeRow1(string $sql): array
332
  {
333
    $this->logQuery($sql);
334
335
    return $this->dl->executeRow1($sql);
336
  }
337
338
  //--------------------------------------------------------------------------------------------------------------------
339
  /**
340
   * Executes a query that returns 0 or more rows.
341
   *
342
   * @param string $sql The SQL statement.
343
   *
344
   * @return array[]
345
   *
346 1
   * @throws MySqlQueryErrorException
347
   */
348 1
  public function executeRows(string $sql): array
349
  {
350 1
    $this->logQuery($sql);
351
352
    return $this->dl->executeRows($sql);
353
  }
354
355
  //--------------------------------------------------------------------------------------------------------------------
356
  /**
357
   * Executes a query that returns 0 or 1 row.
358
   * Throws an exception if the query selects 2 or more rows.
359
   *
360
   * @param string $sql The SQL statement.
361
   *
362
   * @return mixed The selected row.
363
   *
364
   * @throws MySqlQueryErrorException
365 1
   * @throws ResultException
366
   */
367 1
  public function executeSingleton0(string $sql): mixed
368
  {
369 1
    $this->logQuery($sql);
370
371
    return $this->dl->executeSingleton0($sql);
372
  }
373
  //--------------------------------------------------------------------------------------------------------------------
374
  /**
375
   * Executes a query that returns 1 and only 1 row with 1 column.
376
   * Throws an exception if the query selects none, 2 or more rows.
377
   *
378
   * @param string $sql The SQL statement.
379
   *
380
   * @return mixed The selected row.
381
   *
382
   * @throws MySqlQueryErrorException
383 1
   * @throws ResultException
384
   */
385 1
  public function executeSingleton1(string $sql): mixed
386
  {
387 1
    $this->logQuery($sql);
388
389
    return $this->dl->executeSingleton1($sql);
390
  }
391
392
  //--------------------------------------------------------------------------------------------------------------------
393
  /**
394
   * Selects the SQL mode in the order as preferred by MySQL.
395
   *
396
   * @return string
397
   *
398
   * @throws MySqlQueryErrorException
399 1
   * @throws ResultException
400
   */
401 1
  public function getCanonicalSqlMode(): string
402
  {
403 1
    $sql = 'select @@sql_mode';
404
405
    return (string)$this->executeSingleton1($sql);
406
  }
407
408
  //--------------------------------------------------------------------------------------------------------------------
409
  /**
410
   * Selects all labels from a table with labels.
411
   *
412
   * @param string $tableName       The table name.
413
   * @param string $idColumnName    The name of the auto increment column.
414
   * @param string $labelColumnName The name of the column with labels.
415
   *
416
   * @return array[]
417
   *
418 1
   * @throws MySqlQueryErrorException
419
   */
420 1
  public function labelsFromTable(string $tableName, string $idColumnName, string $labelColumnName): array
421
  {
422
    $sql = "
423
select `%s`  as id
424
,      `%s`  as label
425
from   `%s`
426 1
where   nullif(`%s`,'') is not null";
427
428 1
    $sql = sprintf($sql, $idColumnName, $labelColumnName, $tableName, $labelColumnName);
429
430
    return $this->executeRows($sql);
431
  }
432
433
  //--------------------------------------------------------------------------------------------------------------------
434
  /**
435
   * Escapes special characters in a string such that it can be safely used in SQL statements.
436
   *
437
   * MysqlWrapper around [mysqli::real_escape_string](http://php.net/manual/mysqli.real-escape-string.php).
438
   *
439 1
   * @param string $string The string.
440
   *
441 1
   * @return string
442
   */
443
  public function realEscapeString(string $string): string
444
  {
445
    return $this->dl->realEscapeString($string);
446
  }
447
448
  //--------------------------------------------------------------------------------------------------------------------
449
  /**
450
   * Selects the parameters of a stored routine.
451
   *
452
   * @param string $routineName The name of the routine.
453
   *
454 1
   * @return array[]
455
   *
456 1
   * @throws MySqlQueryErrorException
457
   */
458
  public function routineParameters(string $routineName): array
459
  {
460
    $sql = sprintf("
461
select t2.PARAMETER_NAME      as  parameter_name                            
462
,      t2.DATA_TYPE           as  data_type             
463
,      t2.NUMERIC_PRECISION   as  numeric_precision                     
464
,      t2.NUMERIC_SCALE       as  numeric_scale                 
465
,      t2.CHARACTER_SET_NAME  as  character_set_name                      
466
,      t2.COLLATION_NAME      as  collation_name                  
467
,      t2.DTD_IDENTIFIER      as  dtd_identifier                  
468
from information_schema.ROUTINES   t1
469 1
join information_schema.PARAMETERS t2  on  t2.SPECIFIC_SCHEMA = t1.ROUTINE_SCHEMA and
470
                                           t2.SPECIFIC_NAME   = t1.ROUTINE_NAME and
471 1
                                           t2.PARAMETER_MODE  is not null
472
where t1.ROUTINE_SCHEMA = database()
473
and   t1.ROUTINE_NAME   = '%s'",
474
                   $routineName);
475
476
    return $this->executeRows($sql);
477
  }
478
479
  //--------------------------------------------------------------------------------------------------------------------
480
  /**
481
   * Sets the default character set and collation.
482
   *
483
   * @param string $characterSet The character set.
484
   * @param string $collate      The collation.
485
   *
486 1
   * @throws MySqlQueryErrorException
487
   */
488
  public function setCharacterSet(string $characterSet, string $collate): void
489
  {
490
    $sql = sprintf('set names %s collate %s', $this->dl->quoteString($characterSet), $this->dl->quoteString($collate));
491
492
    $this->executeNone($sql);
493
  }
494
495
  //--------------------------------------------------------------------------------------------------------------------
496
  /**
497
   * Sets the SQL mode.
498 1
   *
499
   * @param string $sqlMode The SQL mode.
500 1
   *
501
   * @throws MySqlQueryErrorException
502 1
   */
503
  public function setSqlMode(string $sqlMode): void
504
  {
505
    $sql = sprintf('set sql_mode = %s', $this->dl->quoteString($sqlMode));
506
507
    $this->executeNone($sql);
508
  }
509
510
  //--------------------------------------------------------------------------------------------------------------------
511
  /**
512
   * Selects metadata of a column of table.
513 1
   *
514
   * @param string|null $schemaName The name of the table schema. If null the current schema.
515 1
   * @param string      $tableName  The name of the table.
516
   * @param string      $columnName The name of the column.
517 1
   *
518
   * @return array
519
   *
520
   * @throws MySqlQueryErrorException
521
   */
522
  public function tableColumn(?string $schemaName, string $tableName, string $columnName): array
523
  {
524
    $sql = sprintf('
525
select COLUMN_NAME         as  column_name
526
,      COLUMN_TYPE         as  column_type
527
,      DATA_TYPE           as  data_type
528
,      NUMERIC_PRECISION   as  numeric_precision
529
,      NUMERIC_SCALE       as  numeric_scale
530
,      CHARACTER_SET_NAME  as  character_set_name
531
,      COLLATION_NAME      as  collation_name
532
from   information_schema.COLUMNS
533
where  TABLE_SCHEMA = ifnull(%s, database())
534
and    TABLE_NAME   = %s
535
and    COLUMN_NAME  = %s',
536
                   $this->dl->quoteString($schemaName),
537
                   $this->dl->quoteString($tableName),
538
                   $this->dl->quoteString($columnName));
539
540
    return $this->executeRow1($sql);
541
  }
542
543
  //--------------------------------------------------------------------------------------------------------------------
544
  /**
545
   * Selects metadata of all columns of table.
546
   *
547
   * @param string $schemaName The name of the table schema.
548
   * @param string $tableName  The name of the table.
549
   *
550
   * @return array[]
551
   *
552
   * @throws MySqlQueryErrorException
553
   */
554
  public function tableColumns(string $schemaName, string $tableName): array
555
  {
556
    $sql = sprintf('
557
select COLUMN_NAME         as  column_name
558
,      COLUMN_TYPE         as  column_type
559
,      IS_NULLABLE         as  is_nullable
560
,      CHARACTER_SET_NAME  as  character_set_name
561
,      COLLATION_NAME      as  collation_name
562
,      EXTRA               as  extra
563
from   information_schema.COLUMNS
564
where  TABLE_SCHEMA = %s
565
and    TABLE_NAME   = %s
566
order by ORDINAL_POSITION',
567
                   $this->dl->quoteString($schemaName),
568
                   $this->dl->quoteString($tableName));
569
570
    return $this->executeRows($sql);
571
  }
572
573
  //--------------------------------------------------------------------------------------------------------------------
574
  /**
575
   * Selects the primary key from a table (if any).
576
   *
577
   * @param string $schemaName The name of the table schema.
578
   * @param string $tableName  The name of the table.
579
   *
580
   * @return array[]
581
   *
582
   * @throws MySqlQueryErrorException
583
   */
584
  public function tablePrimaryKey(string $schemaName, string $tableName): array
585
  {
586
    $sql = sprintf('
587
show index from `%s`.`%s`
588
where Key_name = \'PRIMARY\'',
589
                   $schemaName,
590
                   $tableName);
591
592
    return $this->executeRows($sql);
593
  }
594
595
  //--------------------------------------------------------------------------------------------------------------------
596
  /**
597
   * Selects all unique keys from table.
598
   *
599
   * @param string $schemaName The name of the table schema.
600
   * @param string $tableName  The name of the table.
601
   *
602
   * @return array[]
603
   *
604
   * @throws MySqlQueryErrorException
605
   */
606
  public function tableUniqueIndexes(string $schemaName, string $tableName): array
607
  {
608
    $sql = sprintf('
609
show index from `%s`.`%s`
610
where Non_unique = 0',
611
                   $schemaName,
612
                   $tableName);
613
614
    return $this->executeRows($sql);
615
  }
616
617
  //--------------------------------------------------------------------------------------------------------------------
618
  /**
619
   * Logs the query on the console.
620
   *
621
   * @param string $sql The query.
622
   */
623
  private function logQuery(string $sql): void
624
  {
625
    $sql = trim($sql);
626
627
    if (str_contains($sql, "\n"))
628
    {
629
      // Query is a multi line query.
630
      $this->io->logVeryVerbose('Executing query:');
631
      $this->io->logVeryVerbose('<sql>%s</sql>', $sql);
632
    }
633 1
    else
634
    {
635 1
      // Query is a single line query.
636
      $this->io->logVeryVerbose('Executing query: <sql>%s</sql>', $sql);
637 1
    }
638
  }
639
640 1
  //--------------------------------------------------------------------------------------------------------------------
641 1
}
642
643
//----------------------------------------------------------------------------------------------------------------------
644