@@ -23,6 +23,64 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
23
23
*/
24
24
protected $ tableInformation = array ();
25
25
26
+ /**
27
+ * The maximum allowed length for index, primary key and constraint names.
28
+ *
29
+ * Value will usually be set to a 63 chars limit but PostgreSQL allows
30
+ * to higher this value before compiling, so we need to check for that.
31
+ *
32
+ * @var int
33
+ */
34
+ protected $ maxIdentifierLength ;
35
+
36
+ /**
37
+ * Make sure to limit identifiers according to PostgreSQL compiled in length.
38
+ *
39
+ * PostgreSQL allows in standard configuration identifiers no longer than 63
40
+ * chars for table/relation names, indexes, primary keys, and constraints. So
41
+ * we map all identifiers that are too long to drupal_base64hash_tag, where
42
+ * tag is one of:
43
+ * - idx for indexes
44
+ * - key for constraints
45
+ * - pkey for primary keys
46
+ * - seq for sequences
47
+ *
48
+ * @param string $table_identifier_part
49
+ * The first argument used to build the identifier string. This usually
50
+ * refers to a table/relation name.
51
+ * @param string $column_identifier_part
52
+ * The second argument used to build the identifier string. This usually
53
+ * refers to one or more column names.
54
+ * @param string $tag
55
+ * The identifier tag. It can be one of 'idx', 'key', 'pkey' or 'seq'.
56
+ *
57
+ * @return string
58
+ * The index/constraint/pkey identifier.
59
+ */
60
+ protected function ensureIdentifiersLength ($ table_identifier_part , $ column_identifier_part , $ tag ) {
61
+ $ info = $ this ->getPrefixInfo ($ table_identifier_part );
62
+ $ table_identifier_part = $ info ['table ' ];
63
+
64
+ // Filters out potentially empty $column_identifier_part to ensure
65
+ // compatibility with old naming convention (see prefixNonTable()).
66
+ $ identifiers = array_filter (array ($ table_identifier_part , $ column_identifier_part , $ tag ));
67
+ $ identifierName = implode ('_ ' , $ identifiers );
68
+
69
+ // Retrieve the max identifier length which is usually 63 characters
70
+ // but can be altered before PostgreSQL is compiled so we need to check.
71
+ if (empty ($ this ->maxIdentifierLength )) {
72
+ $ this ->maxIdentifierLength = $ this ->connection ->query ("SHOW max_identifier_length " )->fetchField ();
73
+ }
74
+
75
+ if (strlen ($ identifierName ) > $ this ->maxIdentifierLength ) {
76
+ $ saveIdentifier = 'drupal_ ' . $ this ->hashBase64 ($ identifierName ) . '_ ' . $ tag ;
77
+ }
78
+ else {
79
+ $ saveIdentifier = $ identifierName ;
80
+ }
81
+ return $ saveIdentifier ;
82
+ }
83
+
26
84
/**
27
85
* Fetch the list of blobs and sequences used on a table.
28
86
*
@@ -124,11 +182,11 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
124
182
125
183
$ sql_keys = array ();
126
184
if (isset ($ table ['primary key ' ]) && is_array ($ table ['primary key ' ])) {
127
- $ sql_keys [] = 'PRIMARY KEY ( ' . implode (', ' , $ table ['primary key ' ]) . ') ' ;
185
+ $ sql_keys [] = 'CONSTRAINT ' . $ this -> ensureIdentifiersLength ( $ name , '' , ' pkey ' ) . ' PRIMARY KEY ( ' . implode (', ' , $ table ['primary key ' ]) . ') ' ;
128
186
}
129
187
if (isset ($ table ['unique keys ' ]) && is_array ($ table ['unique keys ' ])) {
130
188
foreach ($ table ['unique keys ' ] as $ key_name => $ key ) {
131
- $ sql_keys [] = 'CONSTRAINT ' . $ this ->prefixNonTable ($ name , $ key_name , 'key ' ) . ' UNIQUE ( ' . implode (', ' , $ key ) . ') ' ;
189
+ $ sql_keys [] = 'CONSTRAINT ' . $ this ->ensureIdentifiersLength ($ name , $ key_name , 'key ' ) . ' UNIQUE ( ' . implode (', ' , $ key ) . ') ' ;
132
190
}
133
191
}
134
192
@@ -328,10 +386,31 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
328
386
// rename them when renaming the table.
329
387
$ indexes = $ this ->connection ->query ('SELECT indexname FROM pg_indexes WHERE schemaname = :schema AND tablename = :table ' , array (':schema ' => $ old_schema , ':table ' => $ old_table_name ));
330
388
foreach ($ indexes as $ index ) {
331
- if (preg_match ('/^ ' . preg_quote ($ old_full_name ) . '_(.*)$/ ' , $ index ->indexname , $ matches )) {
389
+ // Get the index type by suffix, e.g. idx/key/pkey
390
+ $ index_type = substr ($ index ->indexname , strrpos ($ index ->indexname , '_ ' ) + 1 );
391
+
392
+ // If the index is already rewritten by ensureIdentifiersLength() to not
393
+ // exceed the 63 chars limit of PostgreSQL, we need to take care of that.
394
+ // Example (drupal_Gk7Su_T1jcBHVuvSPeP22_I3Ni4GrVEgTYlIYnBJkro_idx).
395
+ if (strpos ($ index ->indexname , 'drupal_ ' ) !== FALSE ) {
396
+ preg_match ('/^drupal_(.*)_ ' . preg_quote ($ index_type ) . '/ ' , $ index ->indexname , $ matches );
332
397
$ index_name = $ matches [1 ];
333
- $ this ->connection ->query ('ALTER INDEX ' . $ index ->indexname . ' RENAME TO { ' . $ new_name . '}_ ' . $ index_name );
334
398
}
399
+ else {
400
+ if ($ index_type == 'pkey ' ) {
401
+ // Primary keys do not have a specific name in D7.
402
+ $ index_name = '' ;
403
+ }
404
+ else {
405
+ // Make sure to remove the suffix from index names, because
406
+ // ensureIdentifiersLength() will add the suffix again and thus
407
+ // would result in a wrong index name.
408
+ preg_match ('/^ ' . preg_quote ($ old_full_name ) . '_(.*)_ ' . preg_quote ($ index_type ) . '/ ' , $ index ->indexname , $ matches );
409
+ $ index_name = $ matches [1 ];
410
+ }
411
+ }
412
+
413
+ $ this ->connection ->query ('ALTER INDEX ' . $ index ->indexname . ' RENAME TO ' . $ this ->ensureIdentifiersLength ($ new_name , $ index_name , $ index_type ));
335
414
}
336
415
337
416
// Now rename the table.
@@ -415,8 +494,8 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
415
494
}
416
495
417
496
public function indexExists ($ table , $ name ) {
418
- // Details http ://www.postgresql.org/docs/8.3/interactive /view-pg-indexes.html
419
- $ index_name = ' { ' . $ table . ' }_ ' . $ name . ' _idx ' ;
497
+ // Details https ://www.postgresql.org/docs/10 /view-pg-indexes.html
498
+ $ index_name = $ this -> ensureIdentifiersLength ( $ table, $ name, ' idx ' ) ;
420
499
return (bool ) $ this ->connection ->query ("SELECT 1 FROM pg_indexes WHERE indexname = ' $ index_name' " )->fetchField ();
421
500
}
422
501
@@ -429,7 +508,18 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
429
508
* The name of the constraint (typically 'pkey' or '[constraint]_key').
430
509
*/
431
510
protected function constraintExists ($ table , $ name ) {
432
- $ constraint_name = '{ ' . $ table . '}_ ' . $ name ;
511
+ // ensureIdentifiersLength() expects three parameters, thus we split our
512
+ // constraint name in a proper name and a suffix.
513
+ if ($ name == 'pkey ' ) {
514
+ $ suffix = $ name ;
515
+ $ name = '' ;
516
+ }
517
+ else {
518
+ $ pos = strrpos ($ name , '_ ' );
519
+ $ suffix = substr ($ name , $ pos + 1 );
520
+ $ name = substr ($ name , 0 , $ pos );
521
+ }
522
+ $ constraint_name = $ this ->ensureIdentifiersLength ($ table , $ name , $ suffix );
433
523
return (bool ) $ this ->connection ->query ("SELECT 1 FROM pg_constraint WHERE conname = ' $ constraint_name' " )->fetchField ();
434
524
}
435
525
@@ -441,15 +531,15 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
441
531
throw new DatabaseSchemaObjectExistsException (t ("Cannot add primary key to table @table: primary key already exists. " , array ('@table ' => $ table )));
442
532
}
443
533
444
- $ this ->connection ->query ('ALTER TABLE { ' . $ table . '} ADD PRIMARY KEY ( ' . implode (', ' , $ fields ) . ') ' );
534
+ $ this ->connection ->query ('ALTER TABLE { ' . $ table . '} ADD CONSTRAINT ' . $ this -> ensureIdentifiersLength ( $ table , '' , ' pkey ' ) . ' PRIMARY KEY ( ' . implode (', ' , $ fields ) . ') ' );
445
535
}
446
536
447
537
public function dropPrimaryKey ($ table ) {
448
538
if (!$ this ->constraintExists ($ table , 'pkey ' )) {
449
539
return FALSE ;
450
540
}
451
541
452
- $ this ->connection ->query ('ALTER TABLE { ' . $ table . '} DROP CONSTRAINT ' . $ this ->prefixNonTable ($ table , 'pkey ' ));
542
+ $ this ->connection ->query ('ALTER TABLE { ' . $ table . '} DROP CONSTRAINT ' . $ this ->ensureIdentifiersLength ($ table, '' , 'pkey ' ));
453
543
return TRUE ;
454
544
}
455
545
@@ -461,15 +551,15 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
461
551
throw new DatabaseSchemaObjectExistsException (t ("Cannot add unique key @name to table @table: unique key already exists. " , array ('@table ' => $ table , '@name ' => $ name )));
462
552
}
463
553
464
- $ this ->connection ->query ('ALTER TABLE { ' . $ table . '} ADD CONSTRAINT " ' . $ this ->prefixNonTable ($ table , $ name , 'key ' ) . '" UNIQUE ( ' . implode (', ' , $ fields ) . ') ' );
554
+ $ this ->connection ->query ('ALTER TABLE { ' . $ table . '} ADD CONSTRAINT " ' . $ this ->ensureIdentifiersLength ($ table , $ name , 'key ' ) . '" UNIQUE ( ' . implode (', ' , $ fields ) . ') ' );
465
555
}
466
556
467
557
public function dropUniqueKey ($ table , $ name ) {
468
558
if (!$ this ->constraintExists ($ table , $ name . '_key ' )) {
469
559
return FALSE ;
470
560
}
471
561
472
- $ this ->connection ->query ('ALTER TABLE { ' . $ table . '} DROP CONSTRAINT " ' . $ this ->prefixNonTable ($ table , $ name , 'key ' ) . '" ' );
562
+ $ this ->connection ->query ('ALTER TABLE { ' . $ table . '} DROP CONSTRAINT " ' . $ this ->ensureIdentifiersLength ($ table , $ name , 'key ' ) . '" ' );
473
563
return TRUE ;
474
564
}
475
565
@@ -489,7 +579,7 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
489
579
return FALSE ;
490
580
}
491
581
492
- $ this ->connection ->query ('DROP INDEX ' . $ this ->prefixNonTable ($ table , $ name , 'idx ' ));
582
+ $ this ->connection ->query ('DROP INDEX ' . $ this ->ensureIdentifiersLength ($ table , $ name , 'idx ' ));
493
583
return TRUE ;
494
584
}
495
585
@@ -580,7 +670,7 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
580
670
}
581
671
582
672
protected function _createIndexSql ($ table , $ name , $ fields ) {
583
- $ query = 'CREATE INDEX " ' . $ this ->prefixNonTable ($ table , $ name , 'idx ' ) . '" ON { ' . $ table . '} ( ' ;
673
+ $ query = 'CREATE INDEX " ' . $ this ->ensureIdentifiersLength ($ table , $ name , 'idx ' ) . '" ON { ' . $ table . '} ( ' ;
584
674
$ query .= $ this ->_createKeySql ($ fields ) . ') ' ;
585
675
return $ query ;
586
676
}
@@ -614,4 +704,36 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
614
704
return $ this ->connection ->query ('SELECT obj_description(oid, ?) FROM pg_class WHERE relname = ? ' , array ('pg_class ' , $ info ['table ' ]))->fetchField ();
615
705
}
616
706
}
707
+
708
+ /**
709
+ * Calculates a base-64 encoded, PostgreSQL-safe sha-256 hash per PostgreSQL
710
+ * documentation: 4.1. Lexical Structure.
711
+ *
712
+ * @param $data
713
+ * String to be hashed.
714
+ *
715
+ * @return string
716
+ * A base-64 encoded sha-256 hash, with + and / replaced with _ and any =
717
+ * padding characters removed.
718
+ */
719
+ protected function hashBase64 ($ data ) {
720
+ // Ensure lowercase as D7's pgsql driver does not quote identifiers
721
+ // consistently, and they are therefore folded to lowercase by PostgreSQL.
722
+ $ hash = strtolower (base64_encode (hash ('sha256 ' , $ data , TRUE )));
723
+ // Modify the hash so it's safe to use in PostgreSQL identifiers.
724
+ return strtr ($ hash , array ('+ ' => '_ ' , '/ ' => '_ ' , '= ' => '' ));
725
+ }
726
+
727
+ /**
728
+ * Build a condition to match a table name against a standard information_schema.
729
+ *
730
+ * In PostgreSQL "unquoted names are always folded to lower case." The pgsql
731
+ * driver does not quote table names, so they are therefore always lowercase.
732
+ *
733
+ * @see https://www.postgresql.org/docs/14/sql-syntax-lexical.html
734
+ */
735
+ protected function buildTableNameCondition ($ table_name , $ operator = '= ' , $ add_prefix = TRUE ) {
736
+ return parent ::buildTableNameCondition (strtolower ($ table_name ), $ operator , $ add_prefix );
737
+ }
738
+
617
739
}
0 commit comments