mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Valera V Harseko
3 days ago a4c85dec55394fe3ef6005acff6a66b7fbfc464a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2024-2026 3A Systems, LLC.
 */
package org.opends.server.backends.jdbc;
 
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.RemovalCause;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
 
import java.sql.*;
import java.time.Duration;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
public class CachedConnection implements Connection {
    private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
 
    // What has been reported once already. Every one of these reports a setting rather than an
    // event - a property that is not a number, a url no bound of this class can reach, a driver
    // whose property names are not known here - so it does not become truer by being repeated,
    // and every operation of the backend comes through here.
    // Declared above every field whose initializer can reach warnOnce(): class variable
    // initializers run in textual order (JLS 12.4.2), so a set declared below aliveBypassNanos
    // would still be null the moment a property this class reports on carries a value worth
    // warning about - a window longer than the ttl, or one that is not a number - and the report
    // would leave the class uninitializable rather than merely configured oddly.
    static final Set<String> warnedOnce = ConcurrentHashMap.newKeySet();
 
    static final String TTL_PROPERTY = "org.openidentityplatform.opendj.jdbc.ttl";
    static final long DEFAULT_TTL_MS = 15000;
 
    /**
     * How long a pooled connection is handed out without being validated after it was last proven
     * alive, in ms; 0 validates every borrow, the way this pool did before the window existed.
     * <p>
     * The validation of a connection is a round trip of its own - an empty query on postgresql, a
     * ping on mysql, a round trip of its own on oracle and sql server - and every operation of
     * this backend pays it next to the single statement the operation came for. It earns that on a
     * connection that has been sitting in the pool, which the database or a firewall may have
     * dropped in the meantime; it earns nothing on one that answered a moment ago, which is most
     * of them under load. So a connection proven alive within this window is trusted rather than
     * validated, the way the aliveBypassWindow of HikariCP does it.
     */
    static final String ALIVE_BYPASS_PROPERTY = "org.openidentityplatform.opendj.jdbc.alive.bypass";
    static final long DEFAULT_ALIVE_BYPASS_MS = 500;
 
    /**
     * The longest window this class uses, whatever {@value #ALIVE_BYPASS_PROPERTY} and the
     * {@value #TTL_PROPERTY} it is clamped to say. The clamp to the ttl alone does not bound it:
     * the ttl has no upper bound of its own, and with both set high enough the conversion to
     * nanoseconds saturates - the window then outlasts every reading it is compared against, and
     * no connection of the pool is ever validated again. An hour is already far past what this
     * window is about, which is a connection that answered a moment ago.
     * <p>
     * A compile-time constant, so that it holds its value wherever it is read from: the initializer
     * of {@link #aliveBypassNanos} reaches it, and a field initialized in declaration order would
     * still be 0 there if it were ever moved below (JLS 12.4.2).
     */
    static final long MAX_ALIVE_BYPASS_MS = 60 * 60 * 1000L;
 
    // Read once, at class initialization: every operation of this backend borrows a connection,
    // and the borrow is not the place to parse a system property. Not final so that a test can
    // vary the window without a class loader of its own, and volatile because a non-final static
    // long is written neither atomically nor visibly to the threads reading it (JLS 17.7) - every
    // worker of the backend and every replay thread reads this one.
    static volatile long aliveBypassNanos = TimeUnit.MILLISECONDS.toNanos(getAliveBypassMillis());
 
    /**
     * Bounds the connect and the login of one attempt to establish a connection, in seconds; 0 for
     * no bound of its own - the deadline of {@value #POOL_TIMEOUT_PROPERTY} still bounds the
     * attempt, since it stands for the whole borrow. Setting both to 0 is what leaves a connect
     * unbounded.
     */
    static final String CONNECT_TIMEOUT_PROPERTY = "org.openidentityplatform.opendj.jdbc.connect.timeout";
    static final long DEFAULT_CONNECT_TIMEOUT_SECONDS = 30;
 
    /**
     * Bounds a whole borrow - every connect attempt and every wait for a pooled connection - in
     * seconds; 0 for no bound. Not to the millisecond: the connection in hand is validated
     * whatever the deadline says, and an attempt is never given less than a second, so a borrow
     * can return a validation and a last attempt past it.
     */
    static final String POOL_TIMEOUT_PROPERTY = "org.openidentityplatform.opendj.jdbc.pool.timeout";
    static final long DEFAULT_POOL_TIMEOUT_SECONDS = 60;
 
    /** Bound of the validation of a pooled connection: isValid(0) means "no timeout" in the JDBC contract. */
    static final int VALIDATION_TIMEOUT_SECONDS = 5;
 
    /** 08001, sqlclient_unable_to_establish_sqlconnection: the state of a connect that did not happen. */
    private static final String CONNECT_FAILED_SQL_STATE = "08001";
    /** 53300, too_many_connections: how the standard - and postgresql - reports a server taking no further connection. */
    private static final String CONNECTION_LIMIT_SQL_STATE = "53300";
    /** 57P03, cannot_connect_now: postgresql starting up, shutting down or in recovery. */
    private static final String NOT_ACCEPTING_YET_SQL_STATE = "57P03";
 
    static final long MAX_BACKOFF_MS = 1000;
    static final long STALL_WARNING_AFTER_MS = 1000;
    static final long STALL_WARNING_INTERVAL_MS = 10000;
 
    /** How many links of the cause and getNextException() chains of a failure are looked at. */
    private static final int MAX_CHAIN_LENGTH = 32;
 
    /** What a connection string is cut down to where this cannot tell its credentials from the rest of it. */
    static final String CREDENTIALS_HIDDEN = "<credentials hidden>";
    /**
     * A password standing where neither the userinfo nor the parameters of a url are looked for.
     * The name goes by more than one spelling: mysql numbers the factors of a multi-factor login
     * (password1, password2), and a wallet or a key store carries one under a name of its own
     * (oracle.net.wallet_password, javax.net.ssl.keyStorePassword).
     * The value of one ends at a separator of a connection string or at the first space: a driver
     * is free to name a parameter in the middle of a sentence ("password=hunter2 for user u at
     * h:5432"), and a value class running to the end of the string would take the host, the port
     * and the cause of the failure into the blank along with the password.
     */
    private static final Pattern SECRET_PARAMETER =
        Pattern.compile("(?i)([\\w.]*(password|passwd|pwd)\\d*)\\s*=([^\\s,)&;?]*)");
 
    /** The parameters of a connection string worth keeping in a message: which database, not who connects. */
    private static final Set<String> IDENTIFYING_PARAMETERS = Collections.unmodifiableSet(new HashSet<>(
        Arrays.asList("databasename", "database", "instancename", "currentschema", "servicename")));
 
    // setNetworkTimeout() takes the executor its timeout handling runs on: the drivers it is used
    // with here only set a socket option in it, so it costs a call rather than a thread.
    private static final Executor DIRECT_EXECUTOR = Runnable::run;
 
    // Throttled per connection string: two JDBC backends stalling at once have a stall of their
    // own to report, and a single timestamp would let one of them starve the other. Keyed by the
    // safe form of it, the way warnedOnce below is: a static field of this class outlives every
    // borrow, and the password of the backend has no business in one.
    private static final Map<String, AtomicLong> lastStallWarning = new ConcurrentHashMap<>();
    private static final AtomicLong lastReadBoundWarning = new AtomicLong();
 
    /**
     * When an operation last reported that the database had dropped a connection of a pool, as a
     * {@link System#nanoTime()} reading per connection string. A connection proven alive before
     * that moment is validated on its next borrow whatever the window says: whatever dropped one
     * connection - a restart, a failover, a network that went away - dropped every connection
     * established before it, and the window would otherwise hand out the rest of that generation
     * one by one until the pool runs out of them. It is set by the caller that saw the failure
     * ({@code JDBCStorage}), never by a validation that failed here: an idle connection the server
     * reaped is a routine event, and it says nothing about the connection in use that the pool is
     * about to hand out.
     */
    private static final Map<String, Long> poolDistrustedAt = new ConcurrentHashMap<>();
 
    final Connection parent;
 
    // A deque handed out from the end it is returned to: the connection borrowed next is the one
    // returned last, so under any load the pool keeps reusing its hottest connections instead of
    // walking round every one it ever opened. That is what gives the window above anything to
    // bypass - a connection reached only after a whole cycle of the pool has been idle far longer
    // than the window - and it leaves the connections nothing needs at the cold end of the deque,
    // where the per-connection idle expiry of #878 can find them. Until that lands, the cold end
    // is reached only when the whole pool expires, after DEFAULT_TTL_MS with the backend idle.
    // A deque takes one lock for both of its ends where the queue it replaces took one for each,
    // so a borrow and a return no longer proceed side by side - against the round trip the window
    // above saves, and the connect the reuse saves, that lock is not worth a FIFO handoff.
    static LoadingCache<String, BlockingDeque<CachedConnection>> cached = Caffeine.newBuilder()
        .expireAfterAccess(Duration.ofMillis(getCacheTtlMillis()))
        .removalListener((String key, BlockingDeque<CachedConnection> value, RemovalCause cause) -> {
            for (CachedConnection con : value) {
                try {
                    if (!con.isClosed()) {
                        con.parent.close();
                    }
                } catch (SQLException e) {
                    // ignore
                }
            }
        })
        .build(conStr -> new LinkedBlockingDeque<>());
 
    /**
     * Returns the time after which an idle pooled connection is closed, as configured by the
     * {@value #TTL_PROPERTY} system property. An invalid value is ignored in favor of the default.
     */
    private static long getCacheTtlMillis() {
        return getNonNegativeProperty(TTL_PROPERTY, DEFAULT_TTL_MS, "ms");
    }
 
    /**
     * Returns the alive window, clamped to the {@value #TTL_PROPERTY} an idle pooled connection is
     * kept for and to {@link #MAX_ALIVE_BYPASS_MS} behind it. A window longer than the ttl is one
     * the pool cannot back: it goes on trusting the last answer of a connection past the point the
     * pool would have closed and replaced it, which is a claim about a connection that is no longer
     * there. The ttl has no upper bound of its own, though, so the second clamp is what keeps a
     * value the unit conversion saturates on from leaving every connection of the pool trusted for
     * the life of the server.
     * <p>
     * Read at class initialization, like the ttl it is clamped to, so a value set after that
     * changes neither.
     */
    static long getAliveBypassMillis() {
        long configured = getNonNegativeProperty(ALIVE_BYPASS_PROPERTY, DEFAULT_ALIVE_BYPASS_MS, "ms");
        final long ttl = getCacheTtlMillis();
        if (configured > ttl) {
            warnOnce(ALIVE_BYPASS_PROPERTY + "=" + configured + ">" + ttl,
                "The %s window of %d ms is longer than the %d ms of %s a pooled connection is kept for,"
                    + " and is used as %d ms: a connection trusted for longer than the pool keeps it would"
                    + " be trusted past the point the pool closed it",
                ALIVE_BYPASS_PROPERTY, configured, ttl, TTL_PROPERTY, ttl);
            configured = ttl;
        }
        if (configured > MAX_ALIVE_BYPASS_MS) { // the ttl it was just clamped to has no upper bound of its own
            warnOnce(ALIVE_BYPASS_PROPERTY + ">" + MAX_ALIVE_BYPASS_MS,
                "The %s window of %d ms is longer than the %d ms this pool trusts a connection for at most,"
                    + " and is used as %d ms: a longer one saturates the arithmetic it is compared in and"
                    + " leaves every connection of the pool trusted for the life of the server",
                ALIVE_BYPASS_PROPERTY, configured, MAX_ALIVE_BYPASS_MS, MAX_ALIVE_BYPASS_MS);
            return MAX_ALIVE_BYPASS_MS;
        }
        return configured;
    }
 
    /**
     * Returns the value of a numeric system property, ignoring a value that is not a non-negative
     * number in favor of the default. The unit is the one the property is read in, so that the
     * value the message names is not mistaken for another.
     */
    private static long getNonNegativeProperty(String name, long defaultValue, String unit) {
        final String value = System.getProperty(name);
        if (value != null) {
            try {
                final long parsed = Long.parseLong(value.trim());
                if (parsed >= 0) {
                    return parsed;
                }
            } catch (NumberFormatException ignored) {
            }
            // reported once for this value: both properties are read on every borrow, so a
            // "30s" of a typo would otherwise put two lines in the log per backend operation
            warnOnce(name + "=" + value, "Ignoring invalid value \"%s\" of the %s property, using %d %s",
                value, name, defaultValue, unit);
        }
        return defaultValue;
    }
 
    /** Reports something about a setting once for the life of the jvm, however many borrows meet it. */
    private static void warnOnce(String key, String format, Object... args) {
        if (warnedOnce.add(key)) {
            logger.warn(LocalizableMessage.raw(format, args));
        }
    }
 
    /**
     * The drivers this backend is used with, recognized by the prefix of the connection string,
     * together with the properties that bound one attempt to establish a connection. Not one of
     * them bounds the attempt with a single property: the one named first covers the socket
     * connect, and the login behind it - the reads of the prelogin handshake, of TLS and of
     * authentication, the phase a proxy at its connection limit or a moved VIP leaves unanswered -
     * needs the read bound behind it. That holds for the SQL Server driver too, whose loginTimeout
     * leaves the read of the prelogin answer unbounded - and for pgjdbc, whose loginTimeout is not
     * a bound of the socket at all: Driver.connect hands the login to a daemon thread of its own
     * and abandons it at the timeout, so an unbounded read there leaks a thread and a socket per
     * borrow instead of failing one (CachedConnectionTestCase covers every one of them against a
     * socket that never answers).
     */
    enum ConnectDialect {
        /**
         * postgresql: every property of the three takes seconds. connectTimeout covers the socket
         * connect and socketTimeout the reads of the login: pgjdbc puts an SO_TIMEOUT on the login
         * socket only where socketTimeout is set (ConnectionFactoryImpl.tryConnect, both before and
         * after enableSSL), and it defaults to none. loginTimeout is kept on top of the two for a
         * url naming more than one host, where each of them costs a login of its own - the connect
         * is one budget for all of them, taken from the single System.nanoTime() in front of the
         * loop over the hosts - but it is not a bound this class could rely on alone: Driver.connect
         * runs the login on a daemon thread, gives up on the thread rather than on the login, and
         * the thread stays parked in the read for as long as the read lasts.
         */
        POSTGRES("jdbc:postgresql:", '?',
            new String[]{"connectTimeout", "loginTimeout"}, 1, 0,
            new String[]{"socketTimeout"}, 1, true,
            new int[]{}, new int[]{}),
        /** mysql: both properties take milliseconds; socketTimeout is a socket read timeout that outlives the login. */
        MYSQL("jdbc:mysql:", '?',
            new String[]{"connectTimeout"}, 1000, 0,
            new String[]{"socketTimeout"}, 1000, true,
            new int[]{1040, 1203}, new int[]{1053}),
        /** oracle: both properties take milliseconds; ReadTimeout is a socket read timeout that outlives the login. */
        ORACLE("jdbc:oracle:", '?',
            new String[]{"oracle.net.CONNECT_TIMEOUT"}, 1000, 0,
            // the read bound goes by two names the driver reads: the property set here and the
            // property of oracle net it stands for, inside a tns descriptor by the last segment of
            // either. A bound under one of them is a bound of the administrator, so ours is not set
            // on top of it - and neither of theirs is lifted with ours once the login is through.
            // Only the first of the two is a name the driver also reads out of the system
            // properties (SYSTEM_PROPERTY_NAMES below): oracle.net.READ_TIMEOUT reaches the socket
            // from the connection properties alone, so a -D of it bounds nothing and must not be
            // taken for a bound of theirs.
            // RECV_TIMEOUT is not one of them: it is a parameter of sqlnet.ora and of the listener,
            // and the name does not appear in ojdbc8 at all, so a descriptor carrying one would
            // have taken our bound off a connection that never had one of its own.
            new String[]{"oracle.jdbc.ReadTimeout", "oracle.net.READ_TIMEOUT"}, 1000, true,
            // ORA-01033 and ORA-01034: the instance is starting up or not there yet; ORA-01089:
            // it is shutting down. ORA-12514 is left out of these on purpose - a listener that
            // does not know the service is also what a service name of a typo looks like, forever
            new int[]{20, 12516, 12518, 12519, 12520}, new int[]{1033, 1034, 1089}),
        /**
         * ms sql server: loginTimeout takes seconds, socketTimeout milliseconds; the latter is a
         * socket read timeout that outlives the login. loginTimeout is the one property of the
         * four with a range of its own - SQLServerDriverIntProperty.LOGIN_TIMEOUT is validated
         * against [0, 65535], and a value beyond it fails every connect the driver is asked for.
         */
        MICROSOFT("jdbc:sqlserver:", ';',
            new String[]{"loginTimeout"}, 1, 65535,
            new String[]{"socketTimeout"}, 1000, true,
            // 921 and 922: the database has not been recovered yet, or is being recovered; 927: it
            // is in the middle of a restore; 40613: azure sql reporting it not available for now
            new int[]{17809, 10928, 10929}, new int[]{921, 922, 927, 40613});
 
        final String urlPrefix;
        /** the character that separates the parameters of this dialect from the url in front of them */
        final char parameterSeparator;
        /** the properties bounding the connect: the socket connect, and whatever the driver wraps it in */
        final String[] connectProperties;
        final int connectUnitsPerSecond;
        /** the largest value the driver accepts for a connect property, 0 for a driver that takes any */
        final long maxConnectSeconds;
        /** the read bound of the login: the first name is the one set here, the rest are the names it also goes by */
        final String[] readProperties;
        final int readUnitsPerSecond;
        /** whether the read bound of the login stays in force for every statement issued afterwards */
        final boolean readBoundOutlivesLogin;
        /** the vendor codes of this dialect for "no further connection is accepted" */
        final int[] connectionLimitCodes;
        /** the vendor codes of this dialect for "not accepting connections yet": a database on its way up */
        final int[] notAcceptingYetCodes;
 
        ConnectDialect(String urlPrefix, char parameterSeparator,
                       String[] connectProperties, int connectUnitsPerSecond, long maxConnectSeconds,
                       String[] readProperties, int readUnitsPerSecond, boolean readBoundOutlivesLogin,
                       int[] connectionLimitCodes, int[] notAcceptingYetCodes) {
            this.urlPrefix = urlPrefix;
            this.parameterSeparator = parameterSeparator;
            this.connectProperties = connectProperties;
            this.connectUnitsPerSecond = connectUnitsPerSecond;
            this.maxConnectSeconds = maxConnectSeconds;
            this.readProperties = readProperties;
            this.readUnitsPerSecond = readUnitsPerSecond;
            this.readBoundOutlivesLogin = readBoundOutlivesLogin;
            this.connectionLimitCodes = connectionLimitCodes;
            this.notAcceptingYetCodes = notAcceptingYetCodes;
        }
 
        /** The dialect of a connection string, or null for a driver whose property names are not known here. */
        static ConnectDialect of(String connectionString) {
            final String url = connectionString.toLowerCase(Locale.ROOT);
            for (final ConnectDialect dialect : values()) {
                if (url.startsWith(dialect.urlPrefix)) {
                    return dialect;
                }
            }
            return null;
        }
 
        /**
         * Fills in the properties bounding one connect attempt, leaving out what the administrator
         * bounded themselves - an explicit setting of theirs keeps precedence, on the SQL Server,
         * mysql and oracle drivers because a supplied property outranks the url, and on postgresql
         * because the url outranks the property. A driver with a range of its own for its connect
         * property is not handed a value beyond it: a bound it rejects is no bound at all, it is a
         * connect that never happens.
         * Returns whether a read bound outliving the login was set and has to be lifted once the
         * connection is established.
         */
        boolean bound(String connectionString, Properties properties, long timeoutSeconds) {
            final long connectSeconds = maxConnectSeconds > 0
                ? Math.min(timeoutSeconds, maxConnectSeconds) : timeoutSeconds;
            // The connect side is one budget rather than a set of independent knobs, so a bound of
            // the administrator under any of its names leaves all of them alone. On postgresql
            // connectTimeout bounds the socket connect and loginTimeout the login behind it:
            // filling in the one they left out caps the one they set, and a "?connectTimeout=300"
            // answered with a loginTimeout of ours is a login pgjdbc gives up on at 30 s - Driver
            // .connect branches into its own thread as soon as loginTimeout is anything but 0.
            if (!declared(connectionString, connectProperties)) {
                for (final String property : connectProperties) {
                    properties.setProperty(property, Long.toString(connectSeconds * connectUnitsPerSecond));
                }
            }
            reportBoundTurnedOffInUrl(connectionString);
            if (!declared(connectionString, readProperties)) {
                properties.setProperty(readProperties[0], Long.toString(timeoutSeconds * readUnitsPerSecond));
                return readBoundOutlivesLogin;
            }
            return false;
        }
 
        /**
         * Reports a url that turns a bound off where nothing this class supplies can put one back.
         * A parameter of a postgresql url outranks the property this class hands the driver, so a
         * "socketTimeout=0" there is not a default to be replaced - it is the administrator asking
         * for an unbounded read, and a borrow that meets a database accepting the connection and
         * answering nothing is then parked with no deadline able to reach it.
         */
        private void reportBoundTurnedOffInUrl(String connectionString) {
            if (!urlOutranksProperties()) {
                return;
            }
            // Every one of them, and keyed by the property rather than by the url alone: a url
            // turns off the read bound and the login bound both ("?socketTimeout=0&loginTimeout=0",
            // where the second is the per-host budget of a failover url), and safeUrl() keeps none
            // of the timeout parameters - so a single key would report the first offender and
            // leave the administrator to find the rest of them on their own.
            for (final String[] properties : new String[][]{readProperties, connectProperties}) {
                for (final String property : properties) {
                    final String value = parameterValue(connectionString, property);
                    if (value != null && !isBound(value)) {
                        warnOnce(safeUrl(connectionString) + "|unbounded|" + property,
                            "%s sets \"%s=%s\": a parameter of a postgresql url outranks the property this backend"
                                + " supplies, so that phase of a connect carries no bound. An operation reaching a"
                                + " database that accepts the connection and does not answer stays parked",
                            safeUrl(connectionString), property, value);
                    }
                }
            }
        }
 
        /** Whether a vendor code of this dialect is one that waiting for the database can clear. */
        boolean isWorthRetrying(int errorCode) {
            return contains(connectionLimitCodes, errorCode) || contains(notAcceptingYetCodes, errorCode);
        }
 
        private static boolean contains(int[] codes, int code) {
            for (final int candidate : codes) {
                if (candidate == code) {
                    return true;
                }
            }
            return false;
        }
 
        // Whether the administrator bounded one of these properties themselves. The dialects
        // separate their parameters differently - "?a=1&b=2" (postgresql, mysql), ";a=1;b=2" (sql
        // server), "(A=1)" inside the descriptor of an oracle tns url, where the property also goes
        // by the last segment of its name alone - so a parameter is recognized by the delimiter in
        // front of it and the "=" behind it rather than by parsing the url syntax of every driver.
        // The connection string is not the only channel of theirs: the oracle driver reads some of
        // its properties out of the system properties as well, which is how a whole jvm is bounded
        // with -Doracle.jdbc.ReadTimeout, and a property supplied to a driver outranks the system
        // property without a word - and would then be lifted after the login as if it were ours,
        // leaving a connection with no read bound where the administrator had set one.
        private boolean declared(String connectionString, String... properties) {
            for (final String property : properties) {
                if (declaredInUrl(connectionString, property) || setAsSystemProperty(property)) {
                    return true;
                }
            }
            return false;
        }
 
        /** Whether the connection string bounds this property, under its own name or the last segment of it. */
        private boolean declaredInUrl(String connectionString, String property) {
            if (containsParameter(connectionString, property)) {
                return true;
            }
            final int dot = property.lastIndexOf('.');
            return dot >= 0 && containsParameter(connectionString, property.substring(dot + 1));
        }
 
        // Which names a driver reads out of the system properties, listed rather than told from
        // the shape of the name: ojdbc8 resolves oracle.jdbc.ReadTimeout and
        // oracle.net.CONNECT_TIMEOUT in three tiers (the properties it was supplied, then
        // System.getProperty, then the properties of the data source), while
        // oracle.net.READ_TIMEOUT - a dotted name of the same driver - is read out of the
        // connection properties alone: the six classes carrying the literal hand it to
        // Properties.get, and none of them to System.getProperty. Taking a -D of it for a bound of
        // the administrator would leave the login with no read bound at all - theirs not read by
        // the driver and ours not set, because we believed theirs was in force.
        private static final Set<String> SYSTEM_PROPERTY_NAMES = Collections.unmodifiableSet(new HashSet<>(
            Arrays.asList("oracle.jdbc.ReadTimeout", "oracle.net.CONNECT_TIMEOUT")));
 
        private static boolean setAsSystemProperty(String property) {
            return SYSTEM_PROPERTY_NAMES.contains(property) && isBound(System.getProperty(property));
        }
 
        // pgjdbc parses the url over the properties it was handed - Driver.connect copies them
        // into a flat map and parseURL then writes the parameters of the url on top - so a value
        // standing in a postgresql url is the value the driver uses, and the one supplied here
        // never reaches the socket. A zero there is not a default of the driver to be replaced: it
        // cannot be replaced, and setting ours on top of it would leave this class lifting a read
        // bound the login never had. The other three let a supplied property win, so a zero of
        // theirs is ours to override.
        private boolean urlOutranksProperties() {
            return this == POSTGRES;
        }
 
        /** Whether this property is bounded by the connection string, as the driver of this dialect reads it. */
        private boolean containsParameter(String connectionString, String property) {
            final String value = parameterValue(connectionString, property);
            return value != null && (urlOutranksProperties() || isBound(value));
        }
 
        // Matched the way the driver of this dialect matches it: pgjdbc and Connector/J look their
        // properties up by their exact name - PropertyKey.fromValue answers null for a name of
        // another case and the parameter is then a parameter of nobody, so "?SocketTimeout=" must
        // not be taken for a bound of the administrator - while the SQL Server driver
        // (getNormalizedPropertyName) and the keywords of an oracle descriptor match either way.
        private String parameterValue(String connectionString, String property) {
            final boolean exact = this == POSTGRES || this == MYSQL;
            final String url = exact ? connectionString : connectionString.toLowerCase(Locale.ROOT);
            final String name = exact ? property : property.toLowerCase(Locale.ROOT);
            String value = null;
            for (int i = url.indexOf(name); i >= 0; i = url.indexOf(name, i + name.length())) {
                final int end = i + name.length();
                if (i > 0 && "?&;(,".indexOf(url.charAt(i - 1)) >= 0 && end < url.length() && url.charAt(end) == '=') {
                    // the last of them: a driver parsing a url into a map lets the last assignment stand
                    value = valueOf(url, end + 1);
                }
            }
            return value;
        }
 
        /** The value of the parameter that starts here: up to the delimiter in front of the next one. */
        private static String valueOf(String url, int from) {
            int end = from;
            while (end < url.length() && "&;),?".indexOf(url.charAt(end)) < 0) {
                end++;
            }
            return url.substring(from, end);
        }
 
        /**
         * Whether a value of the administrator bounds anything. Every one of these drivers reads 0
         * as "wait as long as it takes", so a property set to it is not a bound of theirs to stay
         * out of the way of - it is the default this class exists to replace, and one of ours goes
         * on top of it wherever a supplied property outranks the url. Where it does not, on
         * postgresql, the zero stands and is reported instead of being written over. A value that
         * is no number is left to the driver it belongs to.
         */
        private static boolean isBound(String value) {
            if (value == null || value.trim().isEmpty()) {
                return false;
            }
            try {
                return Double.parseDouble(value.trim()) != 0; // pgjdbc takes a float for its loginTimeout
            } catch (NumberFormatException notANumber) {
                return true;
            }
        }
    }
 
    final String connectionString;
    /**
     * Whether this connection may go back into the pool once it is closed. A connection carrying a
     * read bound that could not be lifted serves the borrower waiting for it and is closed
     * afterwards: left in the pool it would fail every statement slower than that bound - an
     * import batch among them - for every borrow the pool hands it to.
     */
    private final boolean poolable;
 
    /**
     * When this connection last answered the database, as a {@link System#nanoTime()} reading:
     * established - the login and the two round trips that set it up have just answered - or
     * validated. It is never stamped on the way back into the pool, although that is where a
     * connection has most recently been used: {@link #close()} ends the transaction, and pgjdbc
     * short-circuits both {@code rollback()} and {@code commit()} when the transaction state is
     * IDLE, so on a borrow that issued no statement - {@code JDBCStorage.open()}, a configuration
     * change that leaves the base DNs alone, an import of nothing - not a byte reaches the server
     * and the stamp would prove nothing, while marking a connection the database may have dropped
     * as the freshest one in the pool. Stamping proof rather than use makes the window mean
     * "validated at most once per window", which is a claim this class can always back.
     * <p>
     * It stands for the moment the connection was <em>asked</em>, not the moment its answer was
     * filed: {@link #distrustPool} is compared against it as an ordering of two moments, and a
     * proof that took a second to arrive would otherwise outlive a drop reported while it was
     * still in flight. Reading it early only ever ages the proof, which costs a validation and
     * never skips one.
     */
    private volatile long lastKnownAliveNanos;
 
    public CachedConnection(String connectionString, Connection parent) {
        this(connectionString, parent, true);
    }
 
    CachedConnection(String connectionString, Connection parent, boolean poolable) {
        this.connectionString = connectionString;
        this.parent = parent;
        this.poolable = poolable;
        this.lastKnownAliveNanos = System.nanoTime();
    }
 
    /**
     * Borrows a connection: a usable one out of the pool, or a newly established one. Bounded in
     * both phases - every operation of this backend, the open of a backend and the import
     * included, comes through here, and an unbounded borrow turns a database that listens but does
     * not answer into a hang rather than into an error the caller can report.
     */
    static Connection getConnection(String connectionString) throws Exception {
        return getConnection(connectionString, true);
    }
 
    /**
     * Borrows a connection, either trusting the alive window of {@value #ALIVE_BYPASS_PROPERTY} or
     * validating whatever comes out of the pool.
     *
     * @param trusted false for a borrow nothing compensates a dropped connection on. What the
     * window trades away is the connection that breaks inside it, and {@code JDBCStorage} takes
     * that off the caller where it can - a write is replayed, a read tells the pool - but the
     * borrows that open a backend, remove its files or start an import have neither: they issue
     * their statements far from the borrow, and the one that opens a backend issues none at all,
     * so a dropped connection would surface out of the {@code rollback()} of its release. Each of
     * them is one borrow of a cold path, where the round trip the window saves is worth nothing.
     */
    static Connection getConnection(String connectionString, boolean trusted) throws Exception {
        final ConnectDialect dialect = ConnectDialect.of(connectionString);
        reportUnknownDialect(connectionString, dialect);
        final long connectTimeoutSeconds = Math.min(
            getNonNegativeProperty(CONNECT_TIMEOUT_PROPERTY, DEFAULT_CONNECT_TIMEOUT_SECONDS, "s"),
            Integer.MAX_VALUE / 1000);
        final long poolTimeoutSeconds = getNonNegativeProperty(POOL_TIMEOUT_PROPERTY, DEFAULT_POOL_TIMEOUT_SECONDS, "s");
        final long startedAt = System.currentTimeMillis();
        final long deadline = (poolTimeoutSeconds == 0 || poolTimeoutSeconds >= Long.MAX_VALUE / 1000)
            ? Long.MAX_VALUE : startedAt + poolTimeoutSeconds * 1000;
        long waitMs = 0;
        long backoffMs = 0;
        int attempts = 0;
        while (true) {
            final CachedConnection pooled = poll(connectionString, waitMs, deadline, trusted);
            if (pooled != null) {
                return pooled;
            }
            attempts++;
            try {
                return connect(connectionString, dialect, attemptSeconds(connectTimeoutSeconds, deadline));
            } catch (SQLException e) {
                // A database that takes no connection for the moment is the failure worth waiting
                // out: it is at its connection limit, and one of ours is going to come back to the
                // pool - or it is on its way up, and the state clears itself in seconds. Everything
                // else - a password that is not accepted, a database that is down, a driver that is
                // not on the classpath - is reported to the caller instead of being retried behind
                // its back.
                if (!isWorthRetrying(e, dialect)) {
                    throw reported(e, connectionString);
                }
                final long remaining = deadline - System.currentTimeMillis();
                if (remaining <= 0) {
                    // 08001, the state of a connect that did not happen, rather than none at all:
                    // this is the failure of a borrow, and a caller reading the state of what it
                    // caught would otherwise see null where the driver's own exception carried one
                    final SQLTimeoutException timeout = new SQLTimeoutException("no connection to "
                        + safeUrl(connectionString) + " could be borrowed within " + poolTimeoutSeconds + "s ("
                        + attempts + " attempts): the database took no connection for the moment and none was"
                        + " returned to the pool, last error: " + redact(e.getMessage(), connectionString),
                        CONNECT_FAILED_SQL_STATE);
                    timeout.initCause(reported(e, connectionString));
                    throw timeout;
                }
                backoffMs = Math.min(backoffMs == 0 ? 1 : backoffMs * 2, MAX_BACKOFF_MS);
                waitMs = Math.min(backoffMs, remaining);
                warnStall(connectionString, attempts, startedAt, e);
            } catch (RuntimeException e) {
                // a driver reporting a connect it will not make as an unchecked failure carries the
                // connection string of the backend in its message as readily as a SQLException does
                throw reportedUnchecked(e, connectionString);
            }
        }
    }
 
    /**
     * Reports a connection string this class knows no bound for. The properties bounding a connect
     * are the ones of a driver, so a driver outside the four leaves every attempt unbounded - and
     * the deadline of the borrow cannot reach into a connect that is already under way, since the
     * driver is the only thing holding the socket.
     */
    private static void reportUnknownDialect(String connectionString, ConnectDialect dialect) {
        if (dialect != null) {
            return;
        }
        final StringBuilder known = new StringBuilder();
        for (final ConnectDialect candidate : ConnectDialect.values()) {
            known.append(known.length() > 0 ? ", " : "").append(candidate.urlPrefix);
        }
        warnOnce(safeUrl(connectionString) + "|unknown-dialect",
            "%s names a driver whose timeout properties are not known to this backend (%s are): a connect to a"
                + " database that accepts it and does not answer is left without a bound, and the %s property"
                + " cannot end it",
            safeUrl(connectionString), known, POOL_TIMEOUT_PROPERTY);
    }
 
    /**
     * The bound of one connect attempt. The deadline of the borrow bounds it as well - the
     * {@value #POOL_TIMEOUT_PROPERTY} property stands for the whole borrow, and an attempt of its
     * own left to run out would overrun it by a full connect timeout. That holds for an attempt
     * the {@value #CONNECT_TIMEOUT_PROPERTY} property gives no bound of its own, too: turning the
     * per-attempt bound off must not turn the bound of the borrow off with it. Never 0 for an
     * attempt that is bounded at all: 0 is the value that stands for no bound. And never past what
     * an int of milliseconds takes - the pool timeout has no upper bound of its own, while the SQL
     * Server driver rejects a socketTimeout beyond Integer.MAX_VALUE outright, failing every
     * connect of that backend with the name of a property nobody typed.
     */
    static long attemptSeconds(long connectTimeoutSeconds, long deadline) {
        if (deadline == Long.MAX_VALUE) {
            // 0 stands for an attempt with no bound of its own and stays 0; anything else is
            // clamped here as well, so that the range holds whichever branch answers
            return connectTimeoutSeconds == 0 ? 0 : Math.min(connectTimeoutSeconds, Integer.MAX_VALUE / 1000);
        }
        final long remainingSeconds = (deadline - System.currentTimeMillis() + 999) / 1000;
        final long bound = connectTimeoutSeconds == 0
            ? remainingSeconds : Math.min(connectTimeoutSeconds, remainingSeconds);
        return Math.max(1, Math.min(bound, Integer.MAX_VALUE / 1000));
    }
 
    /**
     * Takes a usable connection out of the pool, waiting up to waitMs for one to be returned to it.
     * The validation of a connection costs a round trip, and the pool has no upper bound on the
     * number of them it holds, so draining a pool the database no longer answers is given the
     * deadline of the borrow as well: past it, establishing a connection is the faster answer.
     * The connection in hand is always looked at first - trusted or validated, see
     * {@link #isKnownAlive} - whatever the deadline says: a database at its connection limit has
     * no other source of connections than the ones coming back, and one returned to the pool a
     * moment before the deadline is the very connection this borrow waited for. Only a connection
     * the database no longer answers is closed here.
     */
    private static CachedConnection poll(String connectionString, long waitMs, long deadline, boolean trusted)
            throws InterruptedException {
        CachedConnection con = cached.get(connectionString).pollFirst(waitMs, TimeUnit.MILLISECONDS);
        while (con != null) {
            if (isUsable(con, trusted)) {
                return con;
            }
            closeQuietly(con.parent);
            if (System.currentTimeMillis() >= deadline) {
                return null;
            }
            con = cached.get(connectionString).pollFirst();
        }
        return null;
    }
 
    private static boolean isUsable(CachedConnection con, boolean trusted) {
        if (trusted && isKnownAlive(con)) {
            return true;
        }
        // The validation needs a bound of its own: isValid(0) means "no timeout" in the JDBC
        // contract, and a connection whose socket is half-open answers it no sooner than it
        // answers anything else. isValid(n) is not that bound on every driver either - the SQL
        // Server driver turns it into a query timeout (setQueryTimeout, then "SELECT 1"), which
        // needs an answer from the server to fire at all - so the socket is bounded here, for the
        // validation only.
        final int restore = boundValidation(con.parent);
        if (restore == VALIDATION_BOUND_FAILED) {
            // the bound of the validation is not in force, and the driver may well have applied it
            // before failing: validating here would be the unbounded isValid() this exists to
            // avoid, and pooling it would hand out a connection carrying a bound of ours
            return false;
        }
        // Read before the round trip rather than after it: this stamp is what distrustPool() is
        // compared against, as an ordering of two moments. A validation is allowed
        // VALIDATION_TIMEOUT_SECONDS, so a stamp filed once the answer is in can be younger than a
        // drop another operation reported while it was still in flight - and the connection would
        // then be trusted for the rest of the window by the very check that exists to stop it.
        final long provenAt = System.nanoTime();
        boolean usable;
        try {
            usable = con.isValid(VALIDATION_TIMEOUT_SECONDS);
        } catch (SQLException | RuntimeException e) { // a driver reporting the validation as an error: discard it
            // an unchecked failure out of a driver would unwind through poll(), which stands
            // outside every try of the borrow, and leave this connection dequeued and unclosed
            usable = false;
        }
        if (!usable) {
            // On its way out, and the driver knows it: Connector/J answers a failed validation by
            // aborting the connection and the SQL Server driver by terminating it, so putting the
            // previous bound back would fail as well - and warn about a bound of a connection that
            // is about to be closed, over a reaped idle connection that is nobody's problem.
            return false;
        }
        if (restore >= 0 && !setNetworkTimeout(con.parent, restore,
                "the connection is closed rather than pooled")) {
            return false; // it would carry the bound of the validation into every statement
        }
        con.lastKnownAliveNanos = provenAt;
        return true;
    }
 
    /** A connection left alone by {@link #boundValidation}: no bound of ours to put back afterwards. */
    private static final int VALIDATION_BOUND_LEFT_ALONE = -1;
    /** A connection {@link #boundValidation} could not bound, which may still carry the bound it failed to report. */
    private static final int VALIDATION_BOUND_FAILED = -2;
 
    /**
     * Whether a connection can be handed out on the strength of the last answer it gave, without a
     * round trip to ask for another. Three things have to hold: the window is on, the answer is
     * younger than it, and nothing has reported since that the database dropped a connection of
     * this pool.
     * <p>
     * What the window trades away is the connection that breaks inside it: it is handed out, and
     * the failure surfaces on the statement of the caller rather than on the borrow. That is where
     * a connection breaking mid-operation surfaces anyway - but not every caller of this backend
     * reports such a failure to the client, so the trade is not the caller's alone to bear.
     * {@code JDBCStorage} answers it on both sides: a write is replayed on a connection the next
     * attempt borrows of its own, and a read as much as a write marks the pool distrusted, which
     * closes the window for the rest of the generation the dropped connection belonged to.
     */
    private static boolean isKnownAlive(CachedConnection con) {
        final long window = aliveBypassNanos;
        if (window <= 0) {
            return false;
        }
        final long provenAt = con.lastKnownAliveNanos;
        if (System.nanoTime() - provenAt >= window) { // the overflow safe form of the comparison
            return false;
        }
        final Long distrusted = poolDistrustedAt.get(con.connectionString);
        if (distrusted != null && provenAt - distrusted <= 0) { // the overflow safe form of the comparison
            return false;
        }
        // What the validation this replaces also answered: the removalListener above closes every
        // connection it finds in the deque when the pool expires, and it iterates a weakly
        // consistent view, so a connection taken out by a borrow running at the same time can be
        // closed under it. Answered by the driver out of a flag of its own, not by a round trip.
        return !isClosed(con.parent);
    }
 
    /** Whether the driver reports the connection as closed; one that cannot say is not one to trust. */
    private static boolean isClosed(Connection con) {
        try {
            return con.isClosed();
        } catch (SQLException e) {
            return true;
        }
    }
 
    /**
     * Reports that the database dropped a connection of this pool, so that no connection proven
     * alive before now is handed out unvalidated again. It is called by the operation that saw the
     * failure: this class only ever learns of one from the statement it broke, since a borrow
     * inside the window asks the database nothing.
     */
    static void distrustPool(String connectionString) {
        // merge(later of the two) rather than computeIfAbsent().set(): two operations reporting a
        // drop at once would otherwise move the distrust point backwards - the later reading is
        // written first and the earlier one overwrites it - and the AtomicLong of computeIfAbsent
        // is published holding its initial 0 before set() runs, which a borrow racing it reads as
        // "never". Not Math.max: nanoTime() has no defined origin, so the readings are compared by
        // their difference, the way every other comparison of one in this class is.
        poolDistrustedAt.merge(connectionString, System.nanoTime(),
            (reported, now) -> now - reported > 0 ? now : reported);
    }
 
    /**
     * Bounds the socket of a pooled connection for the length of its validation, returning the
     * network timeout to put back afterwards - or {@link #VALIDATION_BOUND_LEFT_ALONE} for a
     * connection left alone, either because the driver does not take a network timeout or because
     * it is bounded at least as tightly already, by a read timeout of the connection string that
     * is not ours to widen.
     * A driver that takes the call and then fails inside it is told apart from both: it is free to
     * have applied the bound before failing, and a connection put back into the pool carrying five
     * seconds of ours fails every statement slower than that for the rest of its life.
     */
    private static int boundValidation(Connection con) {
        final int bound = VALIDATION_TIMEOUT_SECONDS * 1000;
        final int previous;
        try {
            previous = con.getNetworkTimeout();
        } catch (SQLException | RuntimeException e) { // a driver that does not take one: nothing was changed
            return VALIDATION_BOUND_LEFT_ALONE;
        }
        if (previous > 0 && previous <= bound) {
            return VALIDATION_BOUND_LEFT_ALONE;
        }
        try {
            con.setNetworkTimeout(DIRECT_EXECUTOR, bound);
        } catch (SQLException | RuntimeException e) {
            return VALIDATION_BOUND_FAILED;
        }
        // A driver answering a negative timeout is outside the contract of getNetworkTimeout(),
        // where 0 stands for no limit and nothing below it stands for anything. Handed back as it
        // is, it would be one of the two sentinels above: the bound just set would be read as a
        // bound that was never set, and the connection would go into the pool carrying five
        // seconds of ours into every statement of whoever borrows it next.
        return previous < 0 ? 0 : previous;
    }
 
    static CachedConnection connect(String connectionString, ConnectDialect dialect, long connectTimeoutSeconds)
            throws SQLException {
        // A driver is free to write into the map it is handed, so it gets one of its own.
        final Properties properties = new Properties();
        final boolean readBoundSet = dialect != null && connectTimeoutSeconds > 0
            && dialect.bound(connectionString, properties, connectTimeoutSeconds);
        // Read before the connect rather than after it, for the reason isUsable() reads it before
        // the validation: the login answered somewhere inside this attempt, and a stamp taken once
        // it returned could outlive a drop reported while it was still going on.
        final long provenAt = System.nanoTime();
        final Connection conNew = DriverManager.getConnection(connectionString, properties);
        boolean poolable = true;
        try {
            // still under the read bound: both of these are round trips of their own
            conNew.setAutoCommit(false);
            conNew.setTransactionIsolation(TRANSACTION_READ_COMMITTED);
            if (readBoundSet) {
                // a driver that will not take the bound back has warned about it already: the
                // connection serves the borrower that is waiting for it and is closed rather than
                // pooled, so the bound of the login does not outlive it in the pool
                poolable = relaxReadBound(conNew, connectTimeoutSeconds);
            }
        } catch (SQLException | RuntimeException e) { // nothing holds this connection yet: it would leak
            closeQuietly(conNew);
            throw e;
        }
        final CachedConnection established = new CachedConnection(connectionString, conNew, poolable);
        established.lastKnownAliveNanos = provenAt;
        return established;
    }
 
    // The second bound of the login is a socket read timeout on mysql, oracle and sql server, in
    // force for the whole life of the connection: left in place it would break every statement
    // slower than it - an import batch, the statistics of a freshly loaded table - so it is lifted
    // as soon as the login is through, restoring the behaviour of a connection this class
    // established before. A read bound the connection string sets itself is never touched here:
    // it is not set at all, so nothing of the administrator's is lifted along with it. Returns
    // whether the bound is gone - a connection still carrying it must not be pooled.
    // Named by the bound the login was given rather than by the property it came from: with
    // CONNECT_TIMEOUT_PROPERTY at 0 the attempt takes its bound from what is left of the deadline
    // of the borrow, so naming that property would point at the one setting that is not in force.
    private static boolean relaxReadBound(Connection con, long boundSeconds) {
        return setNetworkTimeout(con, 0, "statements taking longer than the " + boundSeconds
            + "s the login of this connection was bounded by fail on it, and it is closed rather than pooled");
    }
 
    /**
     * Puts a network timeout on a connection, reporting a driver that will not take one. The
     * consequence is the caller's to name: the same failure ends a freshly established connection
     * carrying the read bound of its login and a pooled one whose bound could not be put back.
     */
    private static boolean setNetworkTimeout(Connection con, int millis, String consequence) {
        try {
            con.setNetworkTimeout(DIRECT_EXECUTOR, millis);
            return true;
        } catch (SQLException | RuntimeException e) {
            // Throttled rather than reported once for the life of the JVM: every connection this
            // happens to carries a read bound it was never meant to keep, and a statement dying of
            // it hours later needs a warning of its own to be traced back to here.
            final long now = System.currentTimeMillis();
            final long last = lastReadBoundWarning.get();
            if (now - last >= STALL_WARNING_INTERVAL_MS && lastReadBoundWarning.compareAndSet(last, now)) {
                logger.warn(LocalizableMessage.raw(
                    "The read bound of a JDBC connection could not be set to %d ms (%s): %s",
                    millis, e.getMessage(), consequence));
            }
            return false;
        }
    }
 
    /**
     * Whether the database took no connection for the moment, rather than refusing one for good:
     * it is at its connection limit - one of our own connections is on its way back to the pool -
     * or it is not accepting connections yet, the state a database on its way up reports while it
     * recovers - the one JDBCStorage.open() has no second attempt of its own for, so a backend
     * that meets it stays locked down until the server is restarted. Both clear themselves in
     * seconds; every other failure is the caller's to see.
     */
    static boolean isWorthRetrying(SQLException e, ConnectDialect dialect) {
        // a failure of the driver is often wrapped, and a SQLException carries two chains of its
        // own: the causes behind it and the further exceptions of getNextException()
        final Deque<Throwable> pending = new ArrayDeque<>();
        final Set<Throwable> visited = Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
        enqueue(pending, visited, e);
        for (int links = 0; !pending.isEmpty() && links < MAX_CHAIN_LENGTH; links++) {
            final Throwable t = pending.poll();
            if (t instanceof SQLException) {
                final SQLException sql = (SQLException) t;
                final String sqlState = sql.getSQLState();
                if (CONNECTION_LIMIT_SQL_STATE.equals(sqlState) || NOT_ACCEPTING_YET_SQL_STATE.equals(sqlState)
                    || (dialect != null && dialect.isWorthRetrying(sql.getErrorCode()))) {
                    return true;
                }
                enqueue(pending, visited, sql.getNextException());
            }
            enqueue(pending, visited, t.getCause());
        }
        return false;
    }
 
    // A stall has to reach the server log: without it a database accepting no further connection
    // is indistinguishable from a hang. Throttled, since every operation of the backend borrows
    // through here and would otherwise log a copy of its own.
    private static void warnStall(String connectionString, int attempts, long startedAt, SQLException cause) {
        final long now = System.currentTimeMillis();
        if (now - startedAt < STALL_WARNING_AFTER_MS) {
            return;
        }
        final AtomicLong lastOfThisUrl =
            lastStallWarning.computeIfAbsent(safeUrl(connectionString), url -> new AtomicLong());
        final long last = lastOfThisUrl.get();
        if (now - last >= STALL_WARNING_INTERVAL_MS && lastOfThisUrl.compareAndSet(last, now)) {
            logger.warn(LocalizableMessage.raw("%s", stallMessage(connectionString, attempts, now - startedAt, cause)));
        }
    }
 
    /**
     * The stall as it reaches the log. Built apart from the logging of it so that the rule it has
     * to keep - neither the connection string nor the message of the driver reaches a log as it
     * stands - is a rule a test can hold it to.
     */
    static String stallMessage(String connectionString, int attempts, long waitedMs, SQLException cause) {
        return String.format("%s takes no further connection: waiting %d ms for a pooled one so far (%d attempts),"
            + " last error: %s", safeUrl(connectionString), waitedMs, attempts,
            redact(cause.getMessage(), connectionString));
    }
 
    /**
     * The failure of a connect as it may leave this class: the exception itself where nothing of it
     * names the credentials of the backend, and a redacted rebuild of its whole chain where
     * something does. Rebuilt rather than wrapped: a wrapper keeps its cause, and everything that
     * prints a failure prints the causes along with it - a debug build of
     * stackTraceToSingleLineString walks them, the config manager traces them, and
     * RootContainer.open() makes the message of the cause the message of what it throws - so a
     * link left as it stands would carry the password past the wrapper. The SQLState and the
     * vendor code of every link survive it: they are what tells a caller what happened.
     */
    static SQLException reported(SQLException e, String connectionString) {
        return holdsCredentials(e, connectionString)
            ? redactedCopy(e, connectionString, new int[] { MAX_CHAIN_LENGTH })
            : e;
    }
 
    /** The same of an unchecked failure: a driver is free to report a connect it will not make as one. */
    static Exception reportedUnchecked(RuntimeException e, String connectionString) {
        if (!holdsCredentials(e, connectionString)) {
            return e;
        }
        final SQLException redacted = new SQLNonTransientConnectionException(e.getClass().getName()
            + (e.getMessage() == null ? "" : ": " + redact(e.getMessage(), connectionString)),
            CONNECT_FAILED_SQL_STATE);
        redacted.setStackTrace(e.getStackTrace());
        return redacted;
    }
 
    /**
     * Whether anything in the chain of a failure names what a connection string keeps out of the
     * log. A chain longer than this walk is given answers "yes": what is reported unredacted is
     * what this class has looked at whole, and a link it never reached is not that. The cost of
     * being wrong that way is a chain rebuilt - bounded in its turn - while the cost of being
     * wrong the other way is the password of the backend in the server error log.
     */
    private static boolean holdsCredentials(Throwable failure, String connectionString) {
        final Deque<Throwable> pending = new ArrayDeque<>();
        final Set<Throwable> visited = Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
        enqueue(pending, visited, failure);
        for (int links = 0; !pending.isEmpty(); links++) {
            if (links >= MAX_CHAIN_LENGTH) {
                return true;
            }
            final Throwable t = pending.poll();
            final String message = t.getMessage();
            if (message != null && !message.equals(redact(message, connectionString))) {
                return true;
            }
            if (t instanceof SQLException) {
                enqueue(pending, visited, ((SQLException) t).getNextException());
            }
            enqueue(pending, visited, t.getCause());
        }
        return false;
    }
 
    // By identity rather than by equals(): a link of a chain carries a cause and a next exception
    // both, and a driver is free to make the two the same failure. Enqueued twice, a chain of
    // those fans out into a copy of itself at every step and spends the budget of a walk on links
    // it has already looked at - five levels of one are enough to exhaust MAX_CHAIN_LENGTH.
    private static void enqueue(Deque<Throwable> pending, Set<Throwable> visited, Throwable t) {
        if (t != null && visited.add(t)) {
            pending.add(t);
        }
    }
 
    // The budget counts the links this rebuilds, the way holdsCredentials() counts the ones it
    // visits - not how deep it has gone. A link of a chain carries a cause and a next exception
    // both, and a driver is free to make them the same failure, so a bound on depth alone leaves
    // room for a chain that fans out into two copies of itself at every step.
    private static SQLException redactedCopy(SQLException e, String connectionString, int[] budget) {
        budget[0]--;
        final SQLException copy =
            new SQLException(redact(e.getMessage(), connectionString), e.getSQLState(), e.getErrorCode());
        copy.setStackTrace(e.getStackTrace());
        if (e.getNextException() != null) {
            copy.setNextException(budget[0] > 0
                ? redactedCopy(e.getNextException(), connectionString, budget) : droppedTail());
        }
        if (e.getCause() != null) {
            copy.initCause(budget[0] > 0 ? redactedLink(e.getCause(), connectionString, budget) : droppedTail());
        }
        return copy;
    }
 
    // What stands where the budget ran out. Without it the same failure logs its root cause when
    // the url of the backend has no password in it and loses it without a word when it has, which
    // is a report of a connect nobody can read against a report of one they can.
    private static SQLException droppedTail() {
        return new SQLException("the rest of this failure was left out: a chain of more than "
            + MAX_CHAIN_LENGTH + " links is rebuilt only that far");
    }
 
    // A link that is no SQLException keeps its class name in the message: its type is not one this
    // can rebuild, and the name of the failure is what a reader of the log is after.
    private static Throwable redactedLink(Throwable t, String connectionString, int[] budget) {
        if (t instanceof SQLException) {
            return redactedCopy((SQLException) t, connectionString, budget);
        }
        budget[0]--;
        final Throwable copy = new Throwable(t.getClass().getName()
            + (t.getMessage() == null ? "" : ": " + redact(t.getMessage(), connectionString)));
        copy.setStackTrace(t.getStackTrace());
        if (t.getCause() != null) {
            copy.initCause(budget[0] > 0 ? redactedLink(t.getCause(), connectionString, budget) : droppedTail());
        }
        return copy;
    }
 
    /**
     * A message of a driver as it may be logged. A driver is free to put the connection string it
     * was handed into it - the jdk itself does, "No suitable driver found for " + url, which is
     * what the ordinary oracle misconfiguration of a driver jar left out of lib/extensions arrives
     * as - and that connection string is where the credentials of this backend live.
     * What it cannot answer for is a driver quoting back a part of a url it failed to parse:
     * a whole credential is replaced, a fragment of one is not.
     */
    static String redact(String message, String connectionString) {
        if (message == null || message.isEmpty()) {
            return message;
        }
        String redacted = message.replace(connectionString, safeUrl(connectionString));
        // The parameter before the values: a password blanked here is one the loop below no longer
        // finds, while the other way round a "<credentials hidden>" standing behind a "password="
        // would be cut in half by a pattern that ends its value at the first space.
        redacted = SECRET_PARAMETER.matcher(redacted).replaceAll("$1=***");
        for (final String secret : secretsOf(connectionString)) {
            redacted = secretPattern(secret).matcher(redacted)
                .replaceAll(Matcher.quoteReplacement(CREDENTIALS_HIDDEN));
        }
        return redacted;
    }
 
    /**
     * A secret as it is looked for in a message: the value itself, wherever it does not stand
     * inside a longer run of letters and digits. A password is free to be one character long, and
     * a bare one of those is a substring of half the lines a driver writes - a password of "1"
     * takes "ORA-12541: TNS:no listener" apart into a line nobody can read, and it makes every
     * failure of that backend one this class believes names the credentials, so the whole chain is
     * rebuilt as well. A redaction that destroys the diagnostic it protects is the worse of the
     * two failures. What a driver quotes back is a credential standing on its own - between the
     * delimiters of a url, or in a sentence of its own - and that is still replaced.
     */
    private static Pattern secretPattern(String secret) {
        final String before = isAlphanumeric(secret.charAt(0)) ? "(?<![A-Za-z0-9])" : "";
        final String after = isAlphanumeric(secret.charAt(secret.length() - 1)) ? "(?![A-Za-z0-9])" : "";
        return Pattern.compile(before + Pattern.quote(secret) + after);
    }
 
    private static boolean isAlphanumeric(char c) {
        return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
    }
 
    /** What of a connection string must not stand in a message: the credentials safeUrl() takes out of it. */
    private static List<String> secretsOf(String connectionString) {
        final List<String> secrets = new ArrayList<>();
        final ConnectDialect dialect = ConnectDialect.of(connectionString);
        final String separators = dialect == null ? "?;" : String.valueOf(dialect.parameterSeparator);
        final int scheme = connectionString.indexOf(':', "jdbc:".length()) + 1;
        if (scheme <= 0) {
            return secrets;
        }
        final int authority = startOfAuthority(connectionString, scheme);
        if (authority < 0) {
            final int at = connectionString.indexOf('@', scheme);
            if (at > scheme) {
                addSecret(secrets, connectionString.substring(credentialsStart(connectionString, scheme, at), at));
            }
        } else {
            final int end = endOfAuthority(connectionString, authority, separators);
            for (final String host : connectionString.substring(authority, end).split(",", -1)) {
                final int at = host.lastIndexOf('@');
                if (at > 0) {
                    addSecret(secrets, host.substring(0, at));
                }
            }
        }
        final Matcher parameter = SECRET_PARAMETER.matcher(connectionString);
        while (parameter.find()) {
            addSecret(secrets, parameter.group(3));
        }
        return secrets;
    }
 
    // The credentials of one host, and the password inside them without the user name in front of
    // it: a driver quoting a url back names either.
    private static void addSecret(List<String> secrets, String credentials) {
        if (credentials.isEmpty()) {
            return;
        }
        secrets.add(credentials);
        final int password = indexOfAny(credentials, ":/", 0);
        if (password >= 0 && password + 1 < credentials.length()) {
            secrets.add(credentials.substring(password + 1));
        }
    }
 
    // The connection string carries the credentials of the backend - JDBCStorage hands the whole
    // db-directory of the configuration to this class, so the url is the only place they live -
    // and it is never logged as it stands. Three shapes hold them and all three are taken off: the
    // "user/password@" in front of an oracle descriptor; the userinfo of an authority, one per
    // host of it, since a url of Connector/J gives every host credentials of its own
    // ("//u:p@h1:3306,u2:p2@h2:3306"); and the parameters behind their first separator,
    // "?user=...&password=..." on postgresql, mysql and oracle, ";password=..." on sql server.
    // What is left is looked over once more: the key-value host syntax of Connector/J puts a
    // password inside the authority itself ("//address=(host=h)(user=u)(password=p)"), where
    // neither of the first two shapes stands, so a "password=" of any case is blanked out wherever
    // it is left standing.
    //
    // A password is free to hold either of the delimiters, so neither of them is looked for in the
    // whole string. The credentials of an oracle url stand between the subprotocol and the first
    // "@", which is the delimiter of its descriptor, so a "?" of one is part of the password
    // rather than the start of the parameters. Everywhere else they stand inside the authority,
    // between "//" and the path behind it, so a "?" of a password is inside them and an "@" of a
    // parameter value ("?user=u@example.com") is not mistaken for the end of them: the host
    // survives in the message either way.
    //
    // And a url none of this took apart is not logged past its subprotocol. An "@" left standing
    // anywhere but where the credentials of an oracle url ended is one this did not recognize - a
    // password holding a "/" inside an authority, a quoted one holding an "@" - and the host of a
    // stall report is worth less than a password in the server log.
    static String safeUrl(String connectionString) {
        final ConnectDialect dialect = ConnectDialect.of(connectionString);
        final String separators = dialect == null ? "?;" : String.valueOf(dialect.parameterSeparator);
        final int scheme = connectionString.indexOf(':', "jdbc:".length()) + 1; // the end of "jdbc:<subprotocol>:"
        if (scheme <= 0) {
            return CREDENTIALS_HIDDEN;
        }
        final String stripped = stripCredentials(connectionString, scheme, separators);
        final int parameters = indexOfAny(stripped, separators, scheme);
        final String url = parameters < 0 ? stripped : stripped.substring(0, parameters);
        final String redacted = SECRET_PARAMETER.matcher(url).replaceAll("$1=***");
        // an "@" standing anywhere but where the credentials of an oracle url ended is one this
        // did not recognize - a password holding a "/" inside an authority, a quoted one holding
        // an "@" - and the host of a stall report is worth less than a password in the server log
        if (redacted.lastIndexOf('@') > endOfRecognizedCredentials(connectionString, scheme)) {
            return redacted.substring(0, scheme) + CREDENTIALS_HIDDEN;
        }
        return parameters < 0 ? redacted
            : redacted + identifyingParameters(stripped.substring(parameters), dialect);
    }
 
    /**
     * The parameters worth keeping in a message: the ones naming the database rather than whoever
     * connects to it. Two backends of one host answer to the same url up to their parameters, and
     * a stall report that cannot tell them apart is a stall report of neither. Everything else is
     * dropped rather than looked at - a name this does not know is a name free to carry a secret.
     */
    private static String identifyingParameters(String parameters, ConnectDialect dialect) {
        final char separator = dialect == null ? ';' : dialect.parameterSeparator;
        final StringBuilder kept = new StringBuilder();
        for (final String parameter : parameters.split("[?&;]")) {
            final int equals = parameter.indexOf('=');
            if (equals > 0
                && IDENTIFYING_PARAMETERS.contains(parameter.substring(0, equals).toLowerCase(Locale.ROOT))) {
                kept.append(kept.length() == 0 || separator != '?' ? separator : '&').append(parameter);
            }
        }
        return kept.toString();
    }
 
    private static String stripCredentials(String url, int scheme, String separators) {
        final int authority = startOfAuthority(url, scheme);
        if (authority < 0) {
            // no authority: the credentials of an oracle url stand between the subprotocol and the
            // first "@", which is the delimiter of the descriptor behind it - a password holding
            // an "@" of its own has to be quoted for the driver itself
            final int at = url.indexOf('@', scheme);
            return at < 0 ? url : url.substring(0, credentialsStart(url, scheme, at)) + url.substring(at);
        }
        final int end = endOfAuthority(url, authority, separators);
        return url.substring(0, authority) + withoutUserinfo(url.substring(authority, end)) + url.substring(end);
    }
 
    /**
     * Where the credentials of a url that names no authority start: behind the token naming the
     * kind of driver, which stands in front of them ("jdbc:oracle:thin:user/pw@...") and is worth
     * keeping - thin against oci is a first question of an oracle connect. The token is the one
     * right behind the subprotocol rather than the last one in front of the "@", since a password
     * is free to hold a ":" of its own.
     */
    private static int credentialsStart(String url, int scheme, int at) {
        final int driverType = url.indexOf(':', scheme);
        return driverType >= 0 && driverType < at ? driverType + 1 : scheme;
    }
 
    /**
     * The last position a stripped url may still carry an "@" at: where the credentials of an
     * oracle url ended, since the "@" is the delimiter of the descriptor behind them and stays.
     * An authority keeps none of its own - every userinfo of it is taken off, delimiter included.
     */
    private static int endOfRecognizedCredentials(String url, int scheme) {
        final int at = url.indexOf('@', scheme);
        return startOfAuthority(url, scheme) < 0 && at > scheme ? credentialsStart(url, scheme, at) : scheme;
    }
 
    /**
     * Where the hosts of a url of this shape start, or -1 for a url that names no authority. The
     * subprotocol is free to name the kind of connection in front of it - "jdbc:mysql:replication://"
     * - so the "//" is looked for rather than expected right behind the subprotocol. An "@" in
     * front of it belongs to an oracle url ("jdbc:oracle:thin:user/pw@//host"), whose credentials
     * stand where an authority has no place for them.
     */
    private static int startOfAuthority(String url, int scheme) {
        final int slashes = url.indexOf("//", scheme);
        if (slashes < 0 || url.lastIndexOf('@', slashes) >= scheme) {
            return -1;
        }
        // ... and so does a "/" in front of them: it is what separates the credentials of an
        // oracle url ("thin:user/pw@..."), so a password holding a "//" of its own would start an
        // authority inside itself. The "@" ending the credentials stands behind that point, the
        // userinfo taken off is a piece of the password rather than the whole of it, and the "@"
        // the guard of safeUrl() looks for is gone with it - leaving the user name and the head of
        // the password in the message of a stall.
        final int slash = url.indexOf('/', scheme);
        return slash >= 0 && slash < slashes ? -1 : slashes + 2;
    }
 
    /**
     * Where the hosts of an authority end: at the path behind them - a password holds a "?" more
     * readily than a "/" - or at the first parameter of a url that has no path.
     */
    private static int endOfAuthority(String url, int authority, String separators) {
        final int path = url.indexOf('/', authority);
        if (path >= 0) {
            return path;
        }
        final int parameter = indexOfAny(url, separators, authority);
        return parameter < 0 ? url.length() : parameter;
    }
 
    /** The hosts of an authority, each of them without the credentials a url may give it. */
    private static String withoutUserinfo(String authority) {
        final StringBuilder hosts = new StringBuilder();
        final String[] split = authority.split(",", -1);
        for (int i = 0; i < split.length; i++) {
            if (i > 0) { // by the position rather than by what is in hand: a first host may be empty
                hosts.append(',');
            }
            final int at = split[i].lastIndexOf('@');
            hosts.append(at < 0 ? split[i] : split[i].substring(at + 1));
        }
        return hosts.toString();
    }
 
    private static int indexOfAny(String url, String separators, int from) {
        int found = -1;
        for (int i = 0; i < separators.length(); i++) {
            final int at = url.indexOf(separators.charAt(i), from);
            if (at >= 0 && (found < 0 || at < found)) {
                found = at;
            }
        }
        return found;
    }
 
    private static void closeQuietly(Connection con) {
        try {
            con.close();
        } catch (SQLException e) {
            // ignore: it is on its way out anyway
        }
    }
 
    @Override
    public Statement createStatement() throws SQLException {
        return parent.createStatement();
    }
 
    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        return parent.prepareStatement(sql);
    }
 
    @Override
    public CallableStatement prepareCall(String sql) throws SQLException {
        return parent.prepareCall(sql);
    }
 
    @Override
    public String nativeSQL(String sql) throws SQLException {
        return parent.nativeSQL(sql);
    }
 
    @Override
    public void setAutoCommit(boolean autoCommit) throws SQLException {
        parent.setAutoCommit(autoCommit);
    }
 
    @Override
    public boolean getAutoCommit() throws SQLException {
        return parent.getAutoCommit();
    }
 
    @Override
    public void commit() throws SQLException {
        parent.commit();
    }
 
    @Override
    public void rollback() throws SQLException {
        parent.rollback();
    }
 
    @Override
    public void close() throws SQLException {
        try {
            rollback();
        } catch (SQLException e) {
            // A connection that cannot be rolled back must not be handed to the next borrower -
            // and must not be dropped on the floor either: nothing else holds it any more.
            closeQuietly(parent);
            throw e;
        }
        if (!poolable) {
            closeQuietly(parent);
            return;
        }
        // Returned to the end the next borrow takes it from, so that the pool keeps reusing its
        // hottest connections rather than cycling through every one it ever opened.
        cached.get(connectionString).addFirst(this);
    }
 
    @Override
    public boolean isClosed() throws SQLException {
        return parent.isClosed();
    }
 
    @Override
    public DatabaseMetaData getMetaData() throws SQLException {
        return parent.getMetaData();
    }
 
    @Override
    public void setReadOnly(boolean readOnly) throws SQLException {
        parent.setReadOnly(readOnly);
    }
 
    @Override
    public boolean isReadOnly() throws SQLException {
        return parent.isReadOnly();
    }
 
    @Override
    public void setCatalog(String catalog) throws SQLException {
        parent.setCatalog(catalog);
    }
 
    @Override
    public String getCatalog() throws SQLException {
        return parent.getCatalog();
    }
 
    @Override
    public void setTransactionIsolation(int level) throws SQLException {
        parent.setTransactionIsolation(level);
    }
 
    @Override
    public int getTransactionIsolation() throws SQLException {
        return parent.getTransactionIsolation();
    }
 
    @Override
    public SQLWarning getWarnings() throws SQLException {
        return parent.getWarnings();
    }
 
    @Override
    public void clearWarnings() throws SQLException {
        parent.clearWarnings();
    }
 
    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
        return parent.createStatement(resultSetType, resultSetConcurrency);
    }
 
    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        return parent.prepareStatement(sql, resultSetType, resultSetConcurrency);
    }
 
    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        return parent.prepareCall(sql, resultSetType, resultSetConcurrency);
    }
 
    @Override
    public Map<String, Class<?>> getTypeMap() throws SQLException {
        return parent.getTypeMap();
    }
 
    @Override
    public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
        parent.setTypeMap(map);
    }
 
    @Override
    public void setHoldability(int holdability) throws SQLException {
        parent.setHoldability(holdability);
    }
 
    @Override
    public int getHoldability() throws SQLException {
        return parent.getHoldability();
    }
 
    @Override
    public Savepoint setSavepoint() throws SQLException {
        return parent.setSavepoint();
    }
 
    @Override
    public Savepoint setSavepoint(String name) throws SQLException {
        return parent.setSavepoint(name);
    }
 
    @Override
    public void rollback(Savepoint savepoint) throws SQLException {
        parent.rollback(savepoint);
    }
 
    @Override
    public void releaseSavepoint(Savepoint savepoint) throws SQLException {
        parent.releaseSavepoint(savepoint);
    }
 
    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return parent.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
    }
 
    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return parent.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
    }
 
    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return parent.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
    }
 
    @Override
    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
        return parent.prepareStatement(sql, autoGeneratedKeys);
    }
 
    @Override
    public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
        return parent.prepareStatement(sql, columnIndexes);
    }
 
    @Override
    public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
        return parent.prepareStatement(sql, columnNames);
    }
 
    @Override
    public Clob createClob() throws SQLException {
        return parent.createClob();
    }
 
    @Override
    public Blob createBlob() throws SQLException {
        return parent.createBlob();
    }
 
    @Override
    public NClob createNClob() throws SQLException {
        return parent.createNClob();
    }
 
    @Override
    public SQLXML createSQLXML() throws SQLException {
        return parent.createSQLXML();
    }
 
    @Override
    public boolean isValid(int timeout) throws SQLException {
        return parent.isValid(timeout);
    }
 
    @Override
    public void setClientInfo(String name, String value) throws SQLClientInfoException {
        parent.setClientInfo(name, value);
    }
 
    @Override
    public void setClientInfo(Properties properties) throws SQLClientInfoException {
        parent.setClientInfo(properties);
    }
 
    @Override
    public String getClientInfo(String name) throws SQLException {
        return parent.getClientInfo(name);
    }
 
    @Override
    public Properties getClientInfo() throws SQLException {
        return parent.getClientInfo();
    }
 
    @Override
    public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
        return parent.createArrayOf(typeName, elements);
    }
 
    @Override
    public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
        return parent.createStruct(typeName, attributes);
    }
 
    @Override
    public void setSchema(String schema) throws SQLException {
        parent.setSchema(schema);
    }
 
    @Override
    public String getSchema() throws SQLException {
        return parent.getSchema();
    }
 
    @Override
    public void abort(Executor executor) throws SQLException {
        parent.abort(executor);
    }
 
    @Override
    public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
        parent.setNetworkTimeout(executor, milliseconds);
    }
 
    @Override
    public int getNetworkTimeout() throws SQLException {
        return parent.getNetworkTimeout();
    }
 
    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return parent.unwrap(iface);
    }
 
    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return parent.isWrapperFor(iface);
    }
}