Skip to content

Commit df94933

Browse files
author
Isaque Neves
committed
Fix bugs in lost connection detection to automatically reconnect. Updated postgres to 3.1.2 to be able to use the onOpen callback to configure connection settings like setting search path
1 parent 0f0596b commit df94933

10 files changed

+141
-64
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,7 @@ final manager = Manager();
7777
## 3.0.1
7878

7979
- fix bug on query builder count()
80+
81+
## 3.1.2
82+
83+
- Fix bugs in lost connection detection to automatically reconnect. Updated postgres to 3.1.2 to be able to use the onOpen callback to configure connection settings like setting search path

lib/src/capsule/manager.dart

+6-7
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,8 @@ class Manager {
109109
/// @param string $connection
110110
/// @return \Illuminate\Database\Connection
111111
///
112-
Future<Connection> connection([String? connection]) {
113-
return instance!.getConnection(connection);
112+
Future<Connection> connection([String? connection]) async {
113+
return await instance!.getConnection(connection);
114114
}
115115

116116
///
@@ -121,7 +121,7 @@ class Manager {
121121
/// @return \Illuminate\Database\Query\Builder
122122
///
123123
Future<QueryBuilder> table(String table, [String? connectionP]) async {
124-
var com = await instance!.connection(connectionP);
124+
final com = await instance!.connection(connectionP);
125125
return com.table(table);
126126
}
127127

@@ -132,7 +132,7 @@ class Manager {
132132
/// @return \Illuminate\Database\Schema\Builder
133133
///
134134
Future<SchemaBuilder> schema([String? connectionP]) async {
135-
var com = await instance!.connection(connectionP);
135+
final com = await instance!.connection(connectionP);
136136
return com.getSchemaBuilder();
137137
}
138138

@@ -142,8 +142,8 @@ class Manager {
142142
/// @param string $name
143143
/// @return \Illuminate\Database\Connection
144144
///
145-
Future<Connection> getConnection([String? name]) {
146-
return this.manager.connection(name);
145+
Future<Connection> getConnection([String? name]) async {
146+
return await this.manager.connection(name);
147147
}
148148

149149
///
@@ -187,7 +187,6 @@ class Manager {
187187
///
188188
Manager setFetchMode(int fetchMode) {
189189
this.container['config']['database.fetch'] = fetchMode;
190-
191190
return this;
192191
}
193192

lib/src/connection.dart

+14-15
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ class Connection with DetectsLostConnections implements ConnectionInterface {
375375
return [res];
376376
}, timeoutInSeconds);
377377

378-
return resp ;
378+
return resp;
379379
}
380380

381381
///
@@ -577,16 +577,16 @@ class Connection with DetectsLostConnections implements ConnectionInterface {
577577
// Here we will run this query. If an exception occurs we'll determine if it was
578578
// caused by a connection that has been lost. If that is the cause, we'll try
579579
// to re-establish connection and re-run the query with a fresh connection.
580-
// try {
581-
// print('Connection@run');
582-
result = await this
583-
.runQueryCallback(query, bindings, callback, timeoutInSeconds);
584-
//print('Connection@run $result');
585-
// } catch (e) {
586-
// //print('Connection@run error');
587-
// result = await this
588-
// .tryAgainIfCausedByLostConnection(e, query, bindings, callback);
589-
// }
580+
try {
581+
// print('Connection@run');
582+
result = await this
583+
.runQueryCallback(query, bindings, callback, timeoutInSeconds);
584+
//print('Connection@run $result');
585+
} catch (e) {
586+
//print('Connection@run error $e');
587+
result = await this
588+
.tryAgainIfCausedByLostConnection(e, query, bindings, callback, timeoutInSeconds);
589+
}
590590

591591
// Once we have run the query we will calculate the time that it took to run and
592592
// then log the query, bindings, and execution time so we will report them on
@@ -652,19 +652,18 @@ class Connection with DetectsLostConnections implements ConnectionInterface {
652652
Connection, String, dynamic, int? timeoutInSeconds)
653653
callback,
654654
int? timeoutInSeconds,
655-
{int delay = 2000}) async {
655+
{int delay = 1000}) async {
656656
if (this.causedByLostConnection(e) &&
657657
tryReconnectCount < tryReconnectLimit) {
658658
await Future.delayed(Duration(milliseconds: delay));
659-
//print('Eloquent@tryAgainIfCausedByLostConnection try reconnect...');
659+
// print('Eloquent@tryAgainIfCausedByLostConnection try reconnect...');
660660
tryReconnectLimit++;
661661
await this.reconnect();
662-
await Future.delayed(Duration(milliseconds: 1000));
663662
tryReconnectLimit = 0;
664663
return await this
665664
.runQueryCallback(query, bindings, callback, timeoutInSeconds);
666665
}
667-
//print('tryAgainIfCausedByLostConnection');
666+
// print('tryAgainIfCausedByLostConnection');
668667
throw e;
669668
}
670669

lib/src/connectors/postgres_connector.dart

+26-19
Original file line numberDiff line numberDiff line change
@@ -37,34 +37,33 @@ class PostgresConnector extends Connector implements ConnectorInterface {
3737
// and if it has we will issue a statement to modify the timezone with the
3838
// database. Setting this DB timezone is an optional configuration item.
3939

40-
if (config.containsKey('timezone') && config['timezone'] != null) {
41-
var timezone = config['timezone'];
42-
await connection.execute("set time zone '$timezone'");
43-
}
40+
// if (config.containsKey('timezone') && config['timezone'] != null) {
41+
// var timezone = config['timezone'];
42+
// await connection.execute("set time zone '$timezone'");
43+
// }
4444

4545
// Unlike MySQL, Postgres allows the concept of "schema" and a default schema
4646
// may have been specified on the connections. If that is the case we will
4747
// set the default schema search paths to the specified database schema.
48-
if (config.containsKey('schema') && config['schema'] != null) {
49-
var schema = formatSchema(config['schema']);
50-
51-
await connection.execute("set search_path to $schema");
52-
}
48+
// if (config.containsKey('schema') && config['schema'] != null) {
49+
// var schema = formatSchema(config['schema']);
50+
// await connection.execute("set search_path to $schema");
51+
// }
5352

5453
// Postgres allows an application_name to be set by the user and this name is
5554
// used to when monitoring the application with pg_stat_activity. So we'll
5655
// determine if the option has been specified and run a statement if so.
5756

58-
if (config.containsKey('application_name') &&
59-
config['application_name'] != null) {
60-
var applicationName = config['application_name'];
61-
try {
62-
await connection.execute("set application_name to '$applicationName'");
63-
} catch (e) {
64-
print(
65-
'Eloquent: Unable to set the application_name for this PostgreSQL driver.');
66-
}
67-
}
57+
// if (config.containsKey('application_name') &&
58+
// config['application_name'] != null) {
59+
// var applicationName = config['application_name'];
60+
// try {
61+
// await connection.execute("set application_name to '$applicationName'");
62+
// } catch (e) {
63+
// print(
64+
// 'Eloquent: Unable to set the application_name for this PostgreSQL driver.');
65+
// }
66+
// }
6867

6968
return connection;
7069
}
@@ -115,6 +114,14 @@ class PostgresConnector extends Connector implements ConnectorInterface {
115114
dsn += ";application_name=${config['application_name']}";
116115
}
117116

117+
if (config['schema'] != null) {
118+
dsn += ";schema=${config['schema']}";
119+
}
120+
121+
if (config['timezone'] != null) {
122+
dsn += ";timezone=${config['timezone']}";
123+
}
124+
118125
return dsn;
119126
}
120127

lib/src/database_manager.dart

+7-9
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,15 @@ class DatabaseManager implements ConnectionResolverInterface {
4949
Future<Connection> connection([String? nameP]) async {
5050
var re = this.parseConnectionName(nameP);
5151

52+
5253
var name = re[0];
5354
var type = re[1];
5455
// If we haven't created this connection, we'll create it based on the config
5556
// provided in the application. Once we've created the connections we will
5657
// set the "fetch mode" for PDO which determines the query return types.
5758

5859
if (!Utils.isset(this.connectionsProp[name])) {
59-
var connection = await this.makeConnection(name);
60+
final connection = await this.makeConnection(name);
6061

6162
this.setPdoForType(connection, type);
6263

@@ -83,13 +84,12 @@ class DatabaseManager implements ConnectionResolverInterface {
8384
///
8485
/// Disconnect from the given database and remove from local cache.
8586
///
86-
/// @param string $name
87+
/// [name] Connection name
8788
/// @return void
8889
///
89-
void purge([String? name]) {
90-
this.disconnect(name);
91-
this.connectionsProp.remove(name);
92-
//unset(this.connectionsProp[name]);
90+
Future<void> purge([String? name]) async {
91+
await this.disconnect(name);
92+
this.connectionsProp.remove(name);
9393
}
9494

9595
///
@@ -100,7 +100,7 @@ class DatabaseManager implements ConnectionResolverInterface {
100100
///
101101
Future<void> disconnect([String? name]) async {
102102
name = name ?? this.getDefaultConnection();
103-
103+
104104
if (Utils.isset(this.connectionsProp[name])) {
105105
await this.connectionsProp[name].disconnect();
106106
}
@@ -174,8 +174,6 @@ class DatabaseManager implements ConnectionResolverInterface {
174174
/// @return \Illuminate\Database\Connection
175175
///
176176
Connection prepare(Connection connection) {
177-
178-
179177
if (this.app.bound('events')) {
180178
connection.setEventDispatcher(this.app['events']);
181179
}

lib/src/detects_lost_connections.dart

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ mixin DetectsLostConnections {
1212
//TODO revise isso para outros cenários
1313
final isR = Utils.string_contains(message, [
1414
'57P',//for posgresql restart
15-
// 'Can't create a connection',
15+
"Can't create a connection",
1616
'Connection is closed',
17+
'connection is not open',
1718
]);
18-
print('causedByLostConnection $isR');
19+
1920
return isR;
2021
}
2122
}

lib/src/pdo/postgres/postgres_pdo.dart

+16-2
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,24 @@ class PostgresPDO extends PDOInterface {
6161
dsnParser.database,
6262
username: user,
6363
password: password,
64-
encoding: _getEncoding(dsnParser.charset ?? 'utf8'),
64+
encoding: _getEncoding(dsnParser.charset ?? 'utf8'),
6565
);
6666
await connection.open();
67-
await connection.query('''SET client_encoding = '${dsnParser.charset}';''');
67+
68+
if (dsnParser.charset != null) {
69+
await connection.execute("SET client_encoding = '${dsnParser.charset}';");
70+
}
71+
if (dsnParser.schema != null) {
72+
await connection.execute("SET search_path TO '${dsnParser.schema}';");
73+
}
74+
if (dsnParser.timezone != null) {
75+
await connection.execute("SET time zone '${dsnParser.timezone};'");
76+
}
77+
if (dsnParser.applicationName != null) {
78+
await connection
79+
.execute("SET application_name TO '${dsnParser.applicationName};'");
80+
}
81+
6882
return this;
6983
}
7084

lib/src/pdo/postgres_v3/postgres_v3_pdo.dart

+50-7
Original file line numberDiff line numberDiff line change
@@ -73,23 +73,60 @@ class PostgresV3PDO extends PDOInterface {
7373
connection = Pool.withEndpoints(
7474
[endpoint],
7575
settings: PoolSettings(
76+
applicationName: dsnParser.applicationName,
77+
timeZone: dsnParser.timezone,
78+
onOpen: (conn) async {
79+
if (dsnParser.charset != null) {
80+
await conn
81+
.execute("SET client_encoding = '${dsnParser.charset}';");
82+
}
83+
if (dsnParser.schema != null) {
84+
await conn.execute("SET search_path TO '${dsnParser.schema}';");
85+
}
86+
if (dsnParser.timezone != null) {
87+
await conn.execute("SET time zone '${dsnParser.timezone};'");
88+
}
89+
if (dsnParser.applicationName != null) {
90+
await conn.execute(
91+
"SET application_name TO '${dsnParser.applicationName};'");
92+
}
93+
},
7694
maxConnectionCount: dsnParser.poolSize,
7795
encoding: _getEncoding(dsnParser.charset ?? 'utf8'),
7896
sslMode: sslMode,
7997
),
8098
);
8199

82-
await (connection as Pool)
83-
.execute('''SET client_encoding = '${dsnParser.charset}';''');
84-
} else {
100+
//final pool = await (connection as Pool);
101+
} else {
85102
connection = await Connection.open(endpoint,
86103
settings: ConnectionSettings(
104+
applicationName: dsnParser.applicationName,
105+
timeZone: dsnParser.timezone,
106+
onOpen: (conn) async {
107+
if (dsnParser.charset != null) {
108+
await conn
109+
.execute("SET client_encoding = '${dsnParser.charset}';");
110+
}
111+
if (dsnParser.schema != null) {
112+
await conn.execute("SET search_path TO '${dsnParser.schema}';");
113+
}
114+
if (dsnParser.timezone != null) {
115+
await conn.execute("SET time zone '${dsnParser.timezone};'");
116+
}
117+
if (dsnParser.applicationName != null) {
118+
await conn.execute(
119+
"SET application_name TO '${dsnParser.applicationName};'");
120+
}
121+
},
87122
encoding: _getEncoding(dsnParser.charset ?? 'utf8'),
88123
sslMode: sslMode,
89124
));
90125

91-
await (connection as Connection)
92-
.execute('''SET client_encoding = '${dsnParser.charset}';''');
126+
// final conn = await (connection as Connection);
127+
// if (dsnParser.charset != null) {
128+
// conn.execute('''SET client_encoding = '${dsnParser.charset}';''');
129+
// }
93130
}
94131

95132
return this;
@@ -151,13 +188,19 @@ class PostgresV3PDO extends PDOInterface {
151188
maps.add(map);
152189
}
153190
}
154-
191+
155192
final pdoResult = PDOResults(maps, rs.affectedRows);
156193
return pdoResult;
157194
}
158195

159196
@override
160197
Future close() async {
161-
await connection.close();
198+
// print('postgres_v3_pdo@close isOpen ${(connection).isOpen} ');
199+
if (connection is Connection) {
200+
await (connection as Connection).close();
201+
} else if (connection is Pool) {
202+
await (connection as Pool).close();
203+
}
204+
//print('postgres_v3_pdo@close isOpen ${(connection).isOpen} ');
162205
}
163206
}

lib/src/utils/dsn_parser.dart

+13-1
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,10 @@ class DSNParser {
5252
bool get allowReconnect => dsnParts['allowreconnect'].toString() == 'true';
5353
String? get applicationName => dsnParts['application_name'];
5454
String? get sslmode => dsnParts['sslmode'];
55-
Map<String, dynamic>? get options => dsnParts['options'];
55+
String? get timezone => dsnParts['timezone'];
56+
String? get schema => dsnParts['schema'];
5657

58+
Map<String, dynamic>? get options => dsnParts['options'];
5759
Map<String, dynamic> get params => dsnParts['params'];
5860

5961
DSNParser(this.dsn, [this.dsnType = DsnType.pdoPostgreSql]) {
@@ -130,6 +132,16 @@ class DSNParser {
130132
dsnParts['options'] =
131133
parts.lastWhere((p) => p.contains('options=')).split('=').last;
132134
}
135+
136+
if (parts.join().contains('timezone=')) {
137+
dsnParts['timezone'] =
138+
parts.lastWhere((p) => p.contains('timezone=')).split('=').last;
139+
}
140+
141+
if (parts.join().contains('schema=')) {
142+
dsnParts['schema'] =
143+
parts.lastWhere((p) => p.contains('schema=')).split('=').last;
144+
}
133145
} else if (dsnType == DsnType.heroku) {
134146
var patternString = '^' +
135147
'(?:' +

0 commit comments

Comments
 (0)