|
| 1 | +import std.stdio; |
| 2 | +import std.datetime; |
| 3 | +import std.variant; |
| 4 | +import std.conv; |
| 5 | + |
| 6 | +import core.thread : Thread; |
| 7 | +import core.time : seconds; |
| 8 | + |
| 9 | +import ddbc.drivers.pgsqlddbc; |
| 10 | +import ddbc.core; |
| 11 | +import ddbc.common; |
| 12 | + |
| 13 | +import dunit; |
| 14 | +import ddbc.test.common : DdbcTestFixture; |
| 15 | +import ddbc.core : Connection, PreparedStatement, Statement, SQLException, TransactionIsolation; |
| 16 | + |
| 17 | +// Used to control our fake DB connection and when it throws errors. |
| 18 | +bool throwOnConnect = false; |
| 19 | +int connectCount = 0; |
| 20 | +bool throwOnExecute = false; |
| 21 | +int executeCount = 0; |
| 22 | + |
| 23 | +// A fake query result we can use to simulate errors. |
| 24 | +class FakeResultSet : ResultSetImpl { |
| 25 | + override |
| 26 | + void close() { } |
| 27 | +} |
| 28 | + |
| 29 | +// A fake statement we can use to simulate errors on query. |
| 30 | +class FakeStatement : Statement { |
| 31 | + ResultSet executeQuery(string query) { |
| 32 | + if (throwOnExecute) { |
| 33 | + throw new SQLException("Fake execute exception."); |
| 34 | + } |
| 35 | + executeCount++; |
| 36 | + return new FakeResultSet(); |
| 37 | + } |
| 38 | + int executeUpdate(string query) { return 0; } |
| 39 | + int executeUpdate(string query, out Variant insertId) { return 0; } |
| 40 | + void close() { } |
| 41 | + |
| 42 | + DialectType getDialectType() { |
| 43 | + return DialectType.SQLITE; // Just need to pick something. |
| 44 | + } |
| 45 | +} |
| 46 | + |
| 47 | +class FakeConnection : Connection { |
| 48 | + void close() { } |
| 49 | + void commit() { } |
| 50 | + string getCatalog() { return ""; } |
| 51 | + void setCatalog(string catalog) { } |
| 52 | + bool isClosed() { return false; } |
| 53 | + void rollback() { } |
| 54 | + bool getAutoCommit() { return false; } |
| 55 | + void setAutoCommit(bool autoCommit) { } |
| 56 | + Statement createStatement() { return new FakeStatement(); } |
| 57 | + PreparedStatement prepareStatement(string query) { return null;} |
| 58 | + TransactionIsolation getTransactionIsolation() { return TransactionIsolation.READ_COMMITTED; } |
| 59 | + void setTransactionIsolation(TransactionIsolation level) { } |
| 60 | + |
| 61 | + DialectType getDialectType() { |
| 62 | + return DialectType.SQLITE; // Just need to pick something. |
| 63 | + } |
| 64 | +} |
| 65 | + |
| 66 | +// A fake driver we can use to simulate failures to connect. |
| 67 | +class FakeDriver : Driver { |
| 68 | + Connection connect(string url, string[string] params) { |
| 69 | + if (throwOnConnect) { |
| 70 | + throw new SQLException("Fake connect exception."); |
| 71 | + } |
| 72 | + connectCount++; |
| 73 | + return new FakeConnection(); |
| 74 | + } |
| 75 | +} |
| 76 | + |
| 77 | +class ConnectionPoolTest { |
| 78 | + mixin UnitTest; |
| 79 | + |
| 80 | + @Test |
| 81 | + public void testBrokenConnection() { |
| 82 | + Driver driver = new FakeDriver(); |
| 83 | + DataSource dataSource = new ConnectionPoolDataSourceImpl(driver, ""); |
| 84 | + |
| 85 | + // Test verify that when the database is down, nothing can be done. |
| 86 | + throwOnConnect = true; |
| 87 | + throwOnExecute = false; |
| 88 | + try { |
| 89 | + Connection connection = dataSource.getConnection(); |
| 90 | + assert(false, "Expected exception when no connection can be established."); |
| 91 | + } catch (Exception e) { |
| 92 | + // Ignore exception. |
| 93 | + } |
| 94 | + assert(connectCount == 0); |
| 95 | + |
| 96 | + // Obtain a working connection, and validate that it gets recycled. |
| 97 | + throwOnConnect = false; |
| 98 | + throwOnExecute = false; |
| 99 | + Connection connection = dataSource.getConnection(); |
| 100 | + connection.close(); |
| 101 | + connection = dataSource.getConnection(); |
| 102 | + connection.close(); |
| 103 | + assert(connectCount == 1); |
| 104 | + assert(executeCount == 1); |
| 105 | + |
| 106 | + // Create 2 connections, free them, and simulate errors when trying to use them. |
| 107 | + Connection c1 = dataSource.getConnection(); // Use the free connection. |
| 108 | + Connection c2 = dataSource.getConnection(); // Requres a new connection. |
| 109 | + assert(executeCount == 2); |
| 110 | + assert(connectCount == 2); |
| 111 | + c1.close(); |
| 112 | + c2.close(); |
| 113 | + // There are now 2 connections free for re-use, simulate a network disconnect. |
| 114 | + throwOnExecute = true; |
| 115 | + // One connection attempts to be re-used, but it fails and a new one is created. |
| 116 | + Connection c3 = dataSource.getConnection(); |
| 117 | + assert(executeCount == 2); |
| 118 | + assert(connectCount == 3); |
| 119 | + // Restore our network and make sure the 1 remainininig free connect is re-used. |
| 120 | + throwOnExecute = false; |
| 121 | + Connection c4 = dataSource.getConnection(); |
| 122 | + assert(executeCount == 3); |
| 123 | + assert(connectCount == 3); |
| 124 | + // We are now out of free connections, the next attempt should make a new one. |
| 125 | + Connection c5 = dataSource.getConnection(); |
| 126 | + assert(executeCount == 3); |
| 127 | + assert(connectCount == 4); |
| 128 | + } |
| 129 | +} |
0 commit comments