Test Failed
Pull Request — develop (#380)
by Felipe
04:40
created

IndexTrait::addUniqueKey()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 36
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 22
nc 5
nop 4
dl 0
loc 36
rs 8.9457
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * PHPPgAdmin 6.1.3
5
 */
6
7
namespace PHPPgAdmin\Database\Traits;
8
9
use PHPPgAdmin\ADORecordSet;
10
11
/**
12
 * Common trait for indexes and constraints manipulation.
13
 */
14
trait IndexTrait
15
{
16
    /**
17
     * Test if a table has been clustered on an index.
18
     *
19
     * @param string $table The table to test
20
     *
21
     * @return bool true if the table has been already clustered
22
     */
23
    public function alreadyClustered($table)
24
    {
25
        $c_schema = $this->_schema;
26
        $this->clean($c_schema);
27
        $this->clean($table);
28
29
        $sql = \sprintf(
30
            'SELECT i.indisclustered
31
			FROM pg_catalog.pg_class c, pg_catalog.pg_index i
32
			WHERE c.relname = \'%s\'
33
				AND c.oid = i.indrelid AND i.indisclustered
34
				AND c.relnamespace = (SELECT oid FROM pg_catalog.pg_namespace
35
					WHERE nspname=\'%s\')
36
				',
37
            $table,
38
            $c_schema
39
        );
40
41
        $v = $this->selectSet($sql);
42
43
        return !(0 === $v->recordCount());
44
    }
45
46
    /**
47
     * Creates an index.
48
     *
49
     * @param string       $name         The index name (can be blank)
50
     * @param string       $table        The table on which to add the index
51
     * @param array|string $columns      An array of columns that form the index  or a string expression for a functional index
52
     * @param string       $type         The index type
53
     * @param bool         $unique       True if unique, false otherwise
54
     * @param string       $where        Index predicate ('' for none)
55
     * @param string       $tablespace   The tablespaces ('' means none/default)
56
     * @param bool         $concurrently true to create index concurrently
57
     *
58
     * @return array status (0 if operation was successful) and sql sentence
59
     */
60
    public function createIndex($name, $table, $columns, $type, $unique, $where, $tablespace, $concurrently)
61
    {
62
        $f_schema = $this->_schema;
63
        $this->fieldClean($f_schema);
64
        $this->fieldClean($name);
65
        $this->fieldClean($table);
66
67
        $sql = 'CREATE ';
68
69
        $sql .= $unique ? ' UNIQUE ' : '';
70
71
        $sql .= ' INDEX ';
72
73
        $sql .= $concurrently ? ' CONCURRENTLY ' : '';
74
75
        $sql .= $name ? \sprintf(
76
            '  "%s" ',
77
            $name
78
        ) : '';
79
80
        $sql .= \sprintf(
81
            ' ON "%s"."%s" USING %s ',
82
            $f_schema,
83
            $table,
84
            $type
85
        );
86
87
        if (\is_array($columns)) {
88
            $this->arrayClean($columns);
89
            $sql .= '("' . \implode('","', $columns) . '")';
90
        } else {
91
            $sql .= '(' . $columns . ')';
92
        }
93
94
        // Tablespace
95
        if ($this->hasTablespaces() && '' !== $tablespace) {
96
            $this->fieldClean($tablespace);
97
            $sql .= \sprintf(
98
                ' TABLESPACE "%s"',
99
                $tablespace
100
            );
101
        }
102
103
        // Predicate
104
        if ('' !== \trim($where)) {
105
            $sql .= \sprintf(
106
                ' WHERE (%s)',
107
                $where
108
            );
109
        }
110
111
        $status = $this->execute($sql);
112
113
        return [$status, $sql];
114
    }
115
116
    /**
117
     * Removes an index from the database.
118
     *
119
     * @param string $index   The index to drop
120
     * @param bool   $cascade True to cascade drop, false to restrict
121
     *
122
     * @return array<integer,mixed|string> 0 if operation was successful
123
     */
124
    public function dropIndex($index, $cascade)
125
    {
126
        $f_schema = $this->_schema;
127
        $this->fieldClean($f_schema);
128
        $this->fieldClean($index);
129
130
        $sql = \sprintf(
131
            'DROP INDEX "%s"."%s"',
132
            $f_schema,
133
            $index
134
        );
135
136
        if ($cascade) {
137
            $sql .= ' CASCADE';
138
        }
139
140
        $status = $this->execute($sql);
141
142
        return [$status, $sql];
143
    }
144
145
    /**
146
     * Rebuild indexes.
147
     *
148
     * @param string $type  'DATABASE' or 'TABLE' or 'INDEX'
149
     * @param string $name  The name of the specific database, table, or index to be reindexed
150
     * @param bool   $force If true, recreates indexes forcedly in PostgreSQL 7.0-7.1, forces rebuild of system indexes in
151
     *                      7.2-7.3, ignored in >=7.4
152
     *
153
     * @return ADORecordSet|int
154
     */
155
    public function reindex($type, $name, $force = false)
156
    {
157
        $f_schema = $this->_schema;
158
        $this->fieldClean($f_schema);
159
        $this->fieldClean($name);
160
161
        switch ($type) {
162
            case 'DATABASE':
163
                $sql = \sprintf(
164
                    'REINDEX %s "%s"',
165
                    $type,
166
                    $name
167
                );
168
169
                if ($force) {
170
                    $sql .= ' FORCE';
171
                }
172
173
                break;
174
            case 'TABLE':
175
            case 'INDEX':
176
                $sql = \sprintf(
177
                    'REINDEX %s "%s"."%s"',
178
                    $type,
179
                    $f_schema,
180
                    $name
181
                );
182
183
                if ($force) {
184
                    $sql .= ' FORCE';
185
                }
186
187
                break;
188
189
            default:
190
                return -1;
191
        }
192
193
        return $this->execute($sql);
194
    }
195
196
    /**
197
     * Clusters an index.
198
     *
199
     * @param string $table The table the index is on
200
     * @param string $index The name of the index
201
     *
202
     * @return array<integer,mixed|string> 0 if operation was successful
203
     */
204
    public function clusterIndex($table = '', $index = '')
205
    {
206
        $sql = 'CLUSTER';
207
208
        // We don't bother with a transaction here, as there's no point rolling
209
        // back an expensive cluster if a cheap analyze fails for whatever reason
210
211
        if (!empty($table)) {
212
            $f_schema = $this->_schema;
213
            $this->fieldClean($f_schema);
214
            $this->fieldClean($table);
215
            $sql .= \sprintf(
216
                ' "%s"."%s"',
217
                $f_schema,
218
                $table
219
            );
220
221
            if (!empty($index)) {
222
                $this->fieldClean($index);
223
                $sql .= \sprintf(
224
                    ' USING "%s"',
225
                    $index
226
                );
227
            }
228
        }
229
230
        $status = $this->execute($sql);
231
232
        return [$status, $sql];
233
    }
234
235
    /**
236
     * Returns a list of all constraints on a table,
237
     * including constraint name, definition, related col and referenced namespace,
238
     * table and col if needed.
239
     *
240
     * @param string $table the table where we are looking for fk
241
     *
242
     * @return ADORecordSet|int
243
     */
244
    public function getConstraintsWithFields($table)
245
    {
246
        $c_schema = $this->_schema;
247
        $this->clean($c_schema);
248
        $this->clean($table);
249
250
        // get the max number of col used in a constraint for the table
251
        $sql = \sprintf(
252
            'SELECT DISTINCT
253
			max(SUBSTRING(array_dims(c.conkey) FROM  $patern$^\[.*:(.*)\]$$patern$)) as nb
254
		FROM pg_catalog.pg_constraint AS c
255
			JOIN pg_catalog.pg_class AS r ON (c.conrelid=r.oid)
256
			JOIN pg_catalog.pg_namespace AS ns ON (r.relnamespace=ns.oid)
257
		WHERE
258
			r.relname = \'%s\' AND ns.nspname=\'%s\'',
259
            $table,
260
            $c_schema
261
        );
262
263
        $rs = $this->selectSet($sql);
264
265
        if ($rs->EOF) {
266
            $max_col = 0;
267
        } else {
268
            $max_col = $rs->fields['nb'];
269
        }
270
271
        $sql = '
272
			SELECT
273
				c.oid AS conid, c.contype, c.conname, pg_catalog.pg_get_constraintdef(c.oid, true) AS consrc,
274
				ns1.nspname as p_schema, r1.relname as p_table, ns2.nspname as f_schema,
275
				r2.relname as f_table, f1.attname as p_field, f1.attnum AS p_attnum, f2.attname as f_field,
276
				f2.attnum AS f_attnum, pg_catalog.obj_description(c.oid, \'pg_constraint\') AS constcomment,
277
				c.conrelid, c.confrelid
278
			FROM
279
				pg_catalog.pg_constraint AS c
280
				JOIN pg_catalog.pg_class AS r1 ON (c.conrelid=r1.oid)
281
				JOIN pg_catalog.pg_attribute AS f1 ON (f1.attrelid=r1.oid AND (f1.attnum=c.conkey[1]';
282
283
        for ($i = 2; $i <= $rs->fields['nb']; ++$i) {
284
            $sql .= \sprintf(
285
                ' OR f1.attnum=c.conkey[%s]',
286
                $i
287
            );
288
        }
289
        $sql .= '))
290
				JOIN pg_catalog.pg_namespace AS ns1 ON r1.relnamespace=ns1.oid
291
				LEFT JOIN (
292
					pg_catalog.pg_class AS r2 JOIN pg_catalog.pg_namespace AS ns2 ON (r2.relnamespace=ns2.oid)
293
				) ON (c.confrelid=r2.oid)
294
				LEFT JOIN pg_catalog.pg_attribute AS f2 ON
295
					(f2.attrelid=r2.oid AND ((c.confkey[1]=f2.attnum AND c.conkey[1]=f1.attnum)';
296
297
        for ($i = 2; $i <= $rs->fields['nb']; ++$i) {
298
            $sql .= \sprintf(
299
                ' OR (c.confkey[%s]=f2.attnum AND c.conkey[%s]=f1.attnum)',
300
                $i,
301
                $i
302
            );
303
        }
304
305
        $sql .= \sprintf("))
306
			WHERE
307
				r1.relname = '%s' AND ns1.nspname='%s'
308
			ORDER BY 1", $table, $c_schema);
309
310
        return $this->selectSet($sql);
311
    }
312
313
    /**
314
     * Adds a primary key constraint to a table.
315
     *
316
     * @param string $table      The table to which to add the primery key
317
     * @param array  $fields     (array) An array of fields over which to add the primary key
318
     * @param string $name       (optional) The name to give the key, otherwise default name is assigned
319
     * @param string $tablespace (optional) The tablespace for the schema, '' indicates default
320
     *
321
     * @return ADORecordSet|int
322
     */
323
    public function addPrimaryKey($table, $fields, $name = '', $tablespace = '')
324
    {
325
        if (!\is_array($fields) || 0 === \count($fields)) {
326
            return -1;
327
        }
328
329
        $f_schema = $this->_schema;
330
        $this->fieldClean($f_schema);
331
        $this->fieldClean($table);
332
        $this->fieldArrayClean($fields);
333
        $this->fieldClean($name);
334
        $this->fieldClean($tablespace);
335
336
        $sql = \sprintf(
337
            'ALTER TABLE "%s"."%s" ADD ',
338
            $f_schema,
339
            $table
340
        );
341
342
        if ('' !== $name) {
343
            $sql .= \sprintf(
344
                'CONSTRAINT "%s" ',
345
                $name
346
            );
347
        }
348
349
        $sql .= 'PRIMARY KEY ("' . \implode('","', $fields) . '")';
350
351
        if ('' !== $tablespace && $this->hasTablespaces()) {
352
            $sql .= \sprintf(
353
                ' USING INDEX TABLESPACE "%s"',
354
                $tablespace
355
            );
356
        }
357
358
        return $this->execute($sql);
359
    }
360
361
    /**
362
     * Adds a unique constraint to a table.
363
     *
364
     * @param string      $table      The table to which to add the unique key
365
     * @param array|mixed $fields     (array) An array of fields over which to add the unique key
366
     * @param string      $name       (optional) The name to give the key, otherwise default name is assigned
367
     * @param string      $tablespace (optional) The tablespace for the schema, '' indicates default
368
     *
369
     * @return ADORecordSet|int
370
     */
371
    public function addUniqueKey($table, $fields, $name = '', $tablespace = '')
372
    {
373
        if (!\is_array($fields) || 0 === \count($fields)) {
374
            return -1;
375
        }
376
377
        $f_schema = $this->_schema;
378
        $this->fieldClean($f_schema);
379
        $this->fieldClean($table);
380
        $this->fieldArrayClean($fields);
381
        $this->fieldClean($name);
382
        $this->fieldClean($tablespace);
383
384
        $sql = \sprintf(
385
            'ALTER TABLE "%s"."%s" ADD ',
386
            $f_schema,
387
            $table
388
        );
389
390
        if ('' !== $name) {
391
            $sql .= \sprintf(
392
                'CONSTRAINT "%s" ',
393
                $name
394
            );
395
        }
396
397
        $sql .= 'UNIQUE ("' . \implode('","', $fields) . '")';
398
399
        if ('' !== $tablespace && $this->hasTablespaces()) {
400
            $sql .= \sprintf(
401
                ' USING INDEX TABLESPACE "%s"',
402
                $tablespace
403
            );
404
        }
405
406
        return $this->execute($sql);
407
    }
408
409
    // Function functions
410
411
    /**
412
     * Adds a check constraint to a table.
413
     *
414
     * @param string $table      The table to which to add the check
415
     * @param string $definition The definition of the check
416
     * @param string $name       (optional) The name to give the check, otherwise default name is assigned
417
     *
418
     * @return ADORecordSet|int
419
     */
420
    public function addCheckConstraint($table, $definition, $name = '')
421
    {
422
        $f_schema = $this->_schema;
423
        $this->fieldClean($f_schema);
424
        $this->fieldClean($table);
425
        $this->fieldClean($name);
426
        // @@ How the heck do you clean a definition???
427
428
        $sql = \sprintf(
429
            'ALTER TABLE "%s"."%s" ADD ',
430
            $f_schema,
431
            $table
432
        );
433
434
        if ('' !== $name) {
435
            $sql .= \sprintf(
436
                'CONSTRAINT "%s" ',
437
                $name
438
            );
439
        }
440
441
        $sql .= \sprintf(
442
            'CHECK (%s)',
443
            $definition
444
        );
445
446
        return $this->execute($sql);
447
    }
448
449
    /**
450
     * Drops a check constraint from a table.
451
     *
452
     * @param string $table The table from which to drop the check
453
     * @param string $name  The name of the check to be dropped
454
     *
455
     * @return bool|int 0 success
456
     */
457
    public function dropCheckConstraint($table, $name)
458
    {
459
        $f_schema = $this->_schema;
460
        $this->fieldClean($f_schema);
461
        $c_schema = $this->_schema;
462
        $this->clean($c_schema);
463
        $c_table = $table;
464
        $this->fieldClean($table);
465
        $this->clean($c_table);
466
        $this->clean($name);
467
468
        // Begin transaction
469
        $status = $this->beginTransaction();
470
471
        if (0 !== $status) {
472
            return -2;
473
        }
474
475
        // Properly lock the table
476
        $sql = \sprintf(
477
            'LOCK TABLE "%s"."%s" IN ACCESS EXCLUSIVE MODE',
478
            $f_schema,
479
            $table
480
        );
481
        $status = $this->execute($sql);
482
483
        if (0 !== $status) {
484
            $this->rollbackTransaction();
485
486
            return -3;
487
        }
488
489
        // Delete the check constraint
490
        $sql = \sprintf(
491
            'DELETE FROM pg_relcheck WHERE rcrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname=\'%s\'
492
			AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
493
			nspname = \'%s\')) AND rcname=\'%s\'',
494
            $c_table,
495
            $c_schema,
496
            $name
497
        );
498
        $status = $this->execute($sql);
499
500
        if (0 !== $status) {
501
            $this->rollbackTransaction();
502
503
            return -4;
504
        }
505
506
        // Update the pg_class catalog to reflect the new number of checks
507
        $sql = \sprintf(
508
            'UPDATE pg_class SET relchecks=(SELECT COUNT(*) FROM pg_relcheck WHERE
509
					rcrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname=\'%s\'
510
						AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
511
						nspname = \'%s\')))
512
					WHERE relname=\'%s\'',
513
            $c_table,
514
            $c_schema,
515
            $c_table
516
        );
517
        $status = $this->execute($sql);
518
519
        if (0 !== $status) {
520
            $this->rollbackTransaction();
521
522
            return -4;
523
        }
524
525
        // Otherwise, close the transaction
526
        return $this->endTransaction();
527
    }
528
529
    /**
530
     * Adds a foreign key constraint to a table.
531
     *
532
     * @param string $table      The table on which to add an FK
533
     * @param string $targschema The schema that houses the target table to which to add the foreign key
534
     * @param string $targtable  The table to which to add the foreign key
535
     * @param array  $sfields    (array) An array of source fields over which to add the foreign key
536
     * @param array  $tfields    (array) An array of target fields over which to add the foreign key
537
     * @param string $upd_action The action for updates (eg. RESTRICT)
538
     * @param string $del_action The action for deletes (eg. RESTRICT)
539
     * @param string $match      The match type (eg. MATCH FULL)
540
     * @param string $deferrable The deferrability (eg. NOT DEFERRABLE)
541
     * @param string $initially  The initially parameter for the FK (eg. INITIALLY IMMEDIATE)
542
     * @param string $name       [optional] The name to give the key, otherwise default name is assigned
543
     *
544
     * @return ADORecordSet|int
545
     *
546
     * @internal param \PHPPgAdmin\Database\The $target table that contains the target columns
547
     * @internal param \PHPPgAdmin\Database\The $intially initial deferrability (eg. INITIALLY IMMEDIATE)
548
     */
549
    public function addForeignKey(
550
        $table,
551
        $targschema,
552
        $targtable,
553
        $sfields,
554
        $tfields,
555
        $upd_action,
556
        $del_action,
557
        $match,
558
        $deferrable,
559
        $initially,
560
        $name = ''
561
    ) {
562
        if (!\is_array($sfields) || 0 === \count($sfields) ||
563
            !\is_array($tfields) || 0 === \count($tfields)) {
564
            return -1;
565
        }
566
567
        $f_schema = $this->_schema;
568
        $this->fieldClean($f_schema);
569
        $this->fieldClean($table);
570
        $this->fieldClean($targschema);
571
        $this->fieldClean($targtable);
572
        $this->fieldArrayClean($sfields);
573
        $this->fieldArrayClean($tfields);
574
        $this->fieldClean($name);
575
576
        $sql = \sprintf(
577
            'ALTER TABLE "%s"."%s" ADD ',
578
            $f_schema,
579
            $table
580
        );
581
582
        if ('' !== $name) {
583
            $sql .= \sprintf(
584
                'CONSTRAINT "%s" ',
585
                $name
586
            );
587
        }
588
589
        $sql .= 'FOREIGN KEY ("' . \implode('","', $sfields) . '") ';
590
        // Target table needs to be fully qualified
591
        $sql .= \sprintf(
592
            'REFERENCES "%s"."%s"("',
593
            $targschema,
594
            $targtable
595
        ) . \implode('","', $tfields) . '") ';
596
597
        if ($match !== $this->fkmatches[0]) {
598
            $sql .= \sprintf(
599
                ' %s',
600
                $match
601
            );
602
        }
603
604
        if ($upd_action !== $this->fkactions[0]) {
605
            $sql .= \sprintf(
606
                ' ON UPDATE %s',
607
                $upd_action
608
            );
609
        }
610
611
        if ($del_action !== $this->fkactions[0]) {
612
            $sql .= \sprintf(
613
                ' ON DELETE %s',
614
                $del_action
615
            );
616
        }
617
618
        if ($deferrable !== $this->fkdeferrable[0]) {
619
            $sql .= \sprintf(
620
                ' %s',
621
                $deferrable
622
            );
623
        }
624
625
        if ($initially !== $this->fkinitial[0]) {
626
            $sql .= \sprintf(
627
                ' %s',
628
                $initially
629
            );
630
        }
631
632
        return $this->execute($sql);
633
    }
634
635
    /**
636
     * Removes a constraint from a relation.
637
     *
638
     * @param string $constraint The constraint to drop
639
     * @param string $relation   The relation from which to drop
640
     * @param string $type       The type of constraint (c, f, u or p)
641
     * @param bool   $cascade    True to cascade drop, false to restrict
642
     *
643
     * @return ADORecordSet|int
644
     */
645
    public function dropConstraint($constraint, $relation, $type, $cascade)
646
    {
647
        $f_schema = $this->_schema;
648
        $this->fieldClean($f_schema);
649
        $this->fieldClean($constraint);
650
        $this->fieldClean($relation);
651
652
        $sql = \sprintf(
653
            'ALTER TABLE "%s"."%s" DROP CONSTRAINT "%s"',
654
            $f_schema,
655
            $relation,
656
            $constraint
657
        );
658
659
        if ($cascade) {
660
            $sql .= ' CASCADE';
661
        }
662
663
        return $this->execute($sql);
664
    }
665
666
    /**
667
     * A function for getting all columns linked by foreign keys given a group of tables.
668
     *
669
     * @param array $tables multi dimensional assoc array that holds schema and table name
670
     *
671
     * @return ADORecordSet|int recordset of linked tables and columns or -1 if $tables isn't an array
672
     */
673
    public function getLinkingKeys($tables)
674
    {
675
        if (!\is_array($tables)) {
676
            return -1;
677
        }
678
679
        $this->clean($tables[0]['tablename']);
680
        $this->clean($tables[0]['schemaname']);
681
        $tables_list = \sprintf(
682
            '\'%s\'',
683
            $tables[0]['tablename']
684
        );
685
        $schema_list = \sprintf(
686
            '\'%s\'',
687
            $tables[0]['schemaname']
688
        );
689
        $schema_tables_list = \sprintf(
690
            '\'%s.%s\'',
691
            $tables[0]['schemaname'],
692
            $tables[0]['tablename']
693
        );
694
        $tablescount = \count($tables);
695
696
        for ($i = 1; $i < $tablescount; ++$i) {
697
            $this->clean($tables[$i]['tablename']);
698
            $this->clean($tables[$i]['schemaname']);
699
            $tables_list .= \sprintf(
700
                ', \'%s\'',
701
                $tables[$i]['tablename']
702
            );
703
            $schema_list .= \sprintf(
704
                ', \'%s\'',
705
                $tables[$i]['schemaname']
706
            );
707
            $schema_tables_list .= \sprintf(
708
                ', \'%s.%s\'',
709
                $tables[$i]['schemaname'],
710
                $tables[$i]['tablename']
711
            );
712
        }
713
714
        $maxDimension = 1;
715
716
        $sql = \sprintf(
717
            '
718
			SELECT DISTINCT
719
				array_dims(pc.conkey) AS arr_dim,
720
				pgc1.relname AS p_table
721
			FROM
722
				pg_catalog.pg_constraint AS pc,
723
				pg_catalog.pg_class AS pgc1
724
			WHERE
725
				pc.contype = \'f\'
726
				AND (pc.conrelid = pgc1.relfilenode OR pc.confrelid = pgc1.relfilenode)
727
				AND pgc1.relname IN (%s)
728
			',
729
            $tables_list
730
        );
731
732
        //parse our output to find the highest dimension of foreign keys since pc.conkey is stored in an array
733
        $rs = $this->selectSet($sql);
734
735
        while (!$rs->EOF) {
736
            $arrData = \explode(':', $rs->fields['arr_dim']);
737
            $strdimension = \trim(\mb_substr($arrData[1], 0, \mb_strlen($arrData[1]) - 1));
738
            $tmpDimension = (int) $strdimension;
739
            $maxDimension = $tmpDimension > $maxDimension ? $tmpDimension : $maxDimension;
740
            $rs->MoveNext();
741
        }
742
743
        //we know the highest index for foreign keys that conkey goes up to, expand for us in an IN query
744
        $cons_str = '( (pfield.attnum = conkey[1] AND cfield.attnum = confkey[1]) ';
745
746
        for ($i = 2; $i <= $maxDimension; ++$i) {
747
            $cons_str .= \sprintf(
748
                'OR (pfield.attnum = conkey[%s] AND cfield.attnum = confkey[%s]) ',
749
                $i,
750
                $i
751
            );
752
        }
753
        $cons_str .= ') ';
754
755
        $sql = \sprintf(
756
            '
757
			SELECT
758
				pgc1.relname AS p_table,
759
				pgc2.relname AS f_table,
760
				pfield.attname AS p_field,
761
				cfield.attname AS f_field,
762
				pgns1.nspname AS p_schema,
763
				pgns2.nspname AS f_schema
764
			FROM
765
				pg_catalog.pg_constraint AS pc,
766
				pg_catalog.pg_class AS pgc1,
767
				pg_catalog.pg_class AS pgc2,
768
				pg_catalog.pg_attribute AS pfield,
769
				pg_catalog.pg_attribute AS cfield,
770
				(SELECT oid AS ns_id, nspname FROM pg_catalog.pg_namespace WHERE nspname IN (%s) ) AS pgns1,
771
 				(SELECT oid AS ns_id, nspname FROM pg_catalog.pg_namespace WHERE nspname IN (%s) ) AS pgns2
772
			WHERE
773
				pc.contype = \'f\'
774
				AND pgc1.relnamespace = pgns1.ns_id
775
 				AND pgc2.relnamespace = pgns2.ns_id
776
				AND pc.conrelid = pgc1.relfilenode
777
				AND pc.confrelid = pgc2.relfilenode
778
				AND pfield.attrelid = pc.conrelid
779
				AND cfield.attrelid = pc.confrelid
780
				AND %s
781
				AND pgns1.nspname || \'.\' || pgc1.relname IN (%s)
782
				AND pgns2.nspname || \'.\' || pgc2.relname IN (%s)
783
		',
784
            $schema_list,
785
            $schema_list,
786
            $cons_str,
787
            $schema_tables_list,
788
            $schema_tables_list
789
        );
790
791
        return $this->selectSet($sql);
792
    }
793
794
    /**
795
     * Finds the foreign keys that refer to the specified table.
796
     *
797
     * @param string $table The table to find referrers for
798
     *
799
     * @return ADORecordSet|int A recordset or -1 in case of error
800
     */
801
    public function getReferrers($table)
802
    {
803
        $this->clean($table);
804
805
        $status = $this->beginTransaction();
806
807
        if (0 !== $status) {
808
            return -1;
809
        }
810
811
        $c_schema = $this->_schema;
812
        $this->clean($c_schema);
813
814
        $sql = \sprintf(
815
            '
816
			SELECT
817
				pn.nspname,
818
				pl.relname,
819
				pc.conname,
820
				pg_catalog.pg_get_constraintdef(pc.oid) AS consrc
821
			FROM
822
				pg_catalog.pg_constraint pc,
823
				pg_catalog.pg_namespace pn,
824
				pg_catalog.pg_class pl
825
			WHERE
826
				pc.connamespace = pn.oid
827
				AND pc.conrelid = pl.oid
828
				AND pc.contype = \'f\'
829
				AND confrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname=\'%s\'
830
					AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace
831
					WHERE nspname=\'%s\'))
832
			ORDER BY 1,2,3
833
		',
834
            $table,
835
            $c_schema
836
        );
837
838
        return $this->selectSet($sql);
839
    }
840
841
    abstract public function fieldClean(&$str);
842
843
    abstract public function beginTransaction();
844
845
    abstract public function rollbackTransaction();
846
847
    abstract public function endTransaction();
848
849
    abstract public function execute($sql);
850
851
    abstract public function setComment($obj_type, $obj_name, $table, $comment, $basetype = null);
852
853
    abstract public function selectSet($sql);
854
855
    abstract public function clean(&$str);
856
857
    abstract public function hasTablespaces();
858
859
    abstract public function arrayClean(&$arr);
860
861
    abstract public function fieldArrayClean(&$arr);
862
}
863