detector 0.8.2 → 0.8.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +4 -0
- data/bin/detector +58 -17
- data/lib/detector/addons/mariadb.rb +363 -57
- data/lib/detector/addons/mysql.rb +23 -11
- data/lib/detector/version.rb +1 -1
- metadata +23 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2733f54f099af1e09b99cf3e230e1e9e56d3cecf2165026624678e0d6aabacd1
|
4
|
+
data.tar.gz: bb79aaf69655c33b42e662e3206324c6dce9a6883ec1db8cff2abab718ddd754
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6f9d4e59cacff4b3326883ffba9efe29d9446ec0dc0055f5b1618556a3d2bc65e82e039fcf97f05308fdb468b5dcb124019969c28243e54d3bcbaedf7914b7a8
|
7
|
+
data.tar.gz: 5d99d16afca6828dbf8f9ea55d67f750252353ce3108f5abcc35eb663f060594c8f94aa7baaccb876ffb43a63460b6dd63f6f063893db610992e4eb9412a2c19
|
data/README.md
CHANGED
@@ -22,6 +22,10 @@ Or install it yourself as:
|
|
22
22
|
$ gem install detector
|
23
23
|
```
|
24
24
|
|
25
|
+
## Rails Compatibility
|
26
|
+
|
27
|
+
This gem is compatible with Rails 8 and uses flexible version constraints to work with the latest versions of its dependencies. It can be safely included in Rails 8 projects.
|
28
|
+
|
25
29
|
## Usage
|
26
30
|
|
27
31
|
### CLI
|
data/bin/detector
CHANGED
@@ -45,7 +45,27 @@ end
|
|
45
45
|
|
46
46
|
puts "Detector v#{Detector::VERSION}"
|
47
47
|
puts "Detected: #{detector.kind}"
|
48
|
-
|
48
|
+
|
49
|
+
if ENV['DETECTOR_DEBUG']
|
50
|
+
puts "Database: #{detector.uri.path ? detector.uri.path.sub(/^\//, '') : 'none'}"
|
51
|
+
|
52
|
+
# In debug mode, if the detector has connection_error method, show error details
|
53
|
+
if detector.respond_to?(:connection_error) && detector.connection_error
|
54
|
+
error = detector.connection_error
|
55
|
+
puts "CONNECTION ERROR: #{error[:message]} (#{error[:type]}, code: #{error[:error_number]})"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
if detector.version
|
60
|
+
puts "Version: #{detector.version}"
|
61
|
+
else
|
62
|
+
puts "Database connection issue: please check credentials and database name"
|
63
|
+
# Check if we have a max_user_connections error
|
64
|
+
if ENV['DETECTOR_DEBUG'] && detector&.connection.nil?
|
65
|
+
puts "Debug: connection method returned nil, trying connection directly for diagnostics..."
|
66
|
+
puts "Credentials: #{detector.uri.user}:**** @ #{detector.host}:#{detector.port}"
|
67
|
+
end
|
68
|
+
end
|
49
69
|
puts "Host: #{detector.host}:#{detector.port}"
|
50
70
|
|
51
71
|
if detector.respond_to?(:connection_info) && detector.connection_info
|
@@ -58,6 +78,9 @@ if detector.respond_to?(:connection_info) && detector.connection_info
|
|
58
78
|
elsif detector.connection_count && detector.connection_limit
|
59
79
|
usage = detector.connection_usage_percentage
|
60
80
|
puts "Connections: #{detector.connection_count}/#{detector.connection_limit} (#{usage}%)"
|
81
|
+
else
|
82
|
+
# No connection info available
|
83
|
+
puts "Connections: Unable to retrieve connection information"
|
61
84
|
end
|
62
85
|
|
63
86
|
if detector.respond_to?(:replication_available?) && !detector.replication_available?.nil?
|
@@ -86,26 +109,44 @@ end
|
|
86
109
|
|
87
110
|
if detector.databases?
|
88
111
|
db_count = detector.database_count
|
89
|
-
puts "\nDatabases: #{db_count}"
|
112
|
+
puts "\nDatabases: #{db_count || 'Unknown'}"
|
90
113
|
|
91
114
|
if db_count && db_count > 0
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
puts "
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
115
|
+
begin
|
116
|
+
dbs = detector.databases.first(3)
|
117
|
+
if dbs.empty?
|
118
|
+
puts " No databases found or access denied"
|
119
|
+
else
|
120
|
+
dbs.each do |db|
|
121
|
+
db_name = db[:name]
|
122
|
+
puts "\nDatabase: #{db_name} (#{db[:size]})"
|
123
|
+
|
124
|
+
if detector.tables?
|
125
|
+
if db[:table_count]
|
126
|
+
puts " Tables: #{db[:table_count]}"
|
127
|
+
else
|
128
|
+
puts " Tables: #{detector.table_count(db_name) || 'Unknown'}"
|
129
|
+
end
|
130
|
+
|
131
|
+
begin
|
132
|
+
tables = detector.tables(db_name).first(3)
|
133
|
+
if tables.empty?
|
134
|
+
puts " No tables found or access denied"
|
135
|
+
else
|
136
|
+
tables.each do |table|
|
137
|
+
puts " - #{table[:name]}: #{table[:row_count]} rows (#{table[:size]})"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
rescue => e
|
141
|
+
puts " Error retrieving tables: #{e.message}" if ENV['DETECTOR_DEBUG']
|
142
|
+
puts " No tables available (access error)"
|
143
|
+
end
|
144
|
+
end
|
107
145
|
end
|
108
146
|
end
|
147
|
+
rescue => e
|
148
|
+
puts " Error retrieving databases: #{e.message}" if ENV['DETECTOR_DEBUG']
|
149
|
+
puts " No database information available (access error)"
|
109
150
|
end
|
110
151
|
end
|
111
152
|
end
|
@@ -10,43 +10,233 @@ module Detector
|
|
10
10
|
def self.capabilities_for(url)
|
11
11
|
{ sql: true, kv: true, url: url, kind: :mariadb, databases: true, tables: true }
|
12
12
|
end
|
13
|
+
|
14
|
+
# Determine if an error is retriable
|
15
|
+
def retriable_error?(error_number)
|
16
|
+
# List of error codes that might be temporary and worth retrying
|
17
|
+
retriable_codes = [
|
18
|
+
1040, # Too many connections
|
19
|
+
1053, # Server shutdown in progress
|
20
|
+
1077, # Connection refused
|
21
|
+
2002, # Connection refused
|
22
|
+
2003, # Can't connect to MySQL server
|
23
|
+
2006, # MySQL server has gone away
|
24
|
+
2008, # Client ran out of memory
|
25
|
+
2013, # Lost connection during query
|
26
|
+
2026, # SSL connection error
|
27
|
+
2055 # Lost connection to MySQL server at '%s', system error: %d
|
28
|
+
]
|
29
|
+
|
30
|
+
retriable_codes.include?(error_number)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Retry a connection with exponential backoff
|
34
|
+
def with_retry(max_retries = 3)
|
35
|
+
retries = 0
|
36
|
+
|
37
|
+
begin
|
38
|
+
yield
|
39
|
+
rescue Mysql2::Error => e
|
40
|
+
if retriable_error?(e.error_number) && retries < max_retries
|
41
|
+
retries += 1
|
42
|
+
wait_time = 0.5 * (2 ** retries) # Exponential backoff: 1s, 2s, 4s, etc.
|
43
|
+
|
44
|
+
puts "Connection error (#{e.error_number}): #{e.message}. Retrying in #{wait_time}s (attempt #{retries}/#{max_retries})..." if ENV['DETECTOR_DEBUG']
|
45
|
+
|
46
|
+
sleep(wait_time)
|
47
|
+
retry
|
48
|
+
else
|
49
|
+
# Not retriable or max retries reached
|
50
|
+
raise
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Cache for database requests
|
56
|
+
def initialize(url)
|
57
|
+
super
|
58
|
+
@cache = {}
|
59
|
+
end
|
13
60
|
|
61
|
+
def connection
|
62
|
+
# Return cached connection if already established
|
63
|
+
return @conn if @conn && @conn.ping
|
64
|
+
|
65
|
+
# Override the MySQL connection method with MariaDB-specific settings
|
66
|
+
# Handle URI path correctly - strip leading slash if present
|
67
|
+
db_name = uri.path ? uri.path.sub(/^\//, '') : nil
|
68
|
+
|
69
|
+
begin
|
70
|
+
# Try with retry for retriable errors
|
71
|
+
with_retry do
|
72
|
+
# MariaDB-specific connection with fixed init command syntax
|
73
|
+
conn = Mysql2::Client.new(
|
74
|
+
host: host,
|
75
|
+
username: uri.user,
|
76
|
+
password: uri.password,
|
77
|
+
database: db_name,
|
78
|
+
port: port,
|
79
|
+
connect_timeout: 15,
|
80
|
+
read_timeout: 30,
|
81
|
+
write_timeout: 30,
|
82
|
+
reconnect: true
|
83
|
+
# No init_command - caused issues with MariaDB
|
84
|
+
)
|
85
|
+
|
86
|
+
# Test the connection with a simple query
|
87
|
+
conn.query("SELECT 1")
|
88
|
+
@conn = conn
|
89
|
+
end
|
90
|
+
|
91
|
+
@conn
|
92
|
+
rescue Mysql2::Error => e
|
93
|
+
error_message = "MariaDB connection error: #{e.message}"
|
94
|
+
error_type = case
|
95
|
+
when e.error_number == 1226 then "max_user_connections exceeded"
|
96
|
+
when e.error_number == 1045 then "access denied (auth failure)"
|
97
|
+
when e.error_number == 1049 then "unknown database '#{db_name}'"
|
98
|
+
when e.error_number == 2003 then "server unavailable or network error"
|
99
|
+
when e.error_number == 2005 then "unknown host"
|
100
|
+
when e.error_number == 2006 then "server gone away"
|
101
|
+
when e.error_number == 2013 then "connection lost"
|
102
|
+
else "general error"
|
103
|
+
end
|
104
|
+
|
105
|
+
puts "#{error_message} [#{error_type}]" if ENV['DETECTOR_DEBUG']
|
106
|
+
|
107
|
+
# Store the error information
|
108
|
+
@cache[:connection_error] = {
|
109
|
+
message: e.message,
|
110
|
+
type: error_type,
|
111
|
+
error_number: e.error_number,
|
112
|
+
retriable: retriable_error?(e.error_number)
|
113
|
+
}
|
114
|
+
|
115
|
+
nil
|
116
|
+
rescue => e
|
117
|
+
# For non-MySQL errors, still capture them
|
118
|
+
puts "General connection error: #{e.class} - #{e.message}" if ENV['DETECTOR_DEBUG']
|
119
|
+
@cache[:connection_error] = {
|
120
|
+
message: e.message,
|
121
|
+
type: "general error",
|
122
|
+
error_number: 0,
|
123
|
+
retriable: false
|
124
|
+
}
|
125
|
+
nil
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Method to execute queries with retry for retriable errors
|
130
|
+
def execute_query(query)
|
131
|
+
return nil unless connection
|
132
|
+
|
133
|
+
begin
|
134
|
+
with_retry do
|
135
|
+
connection.query(query)
|
136
|
+
end
|
137
|
+
rescue => e
|
138
|
+
puts "Query execution error: #{e.message}" if ENV['DETECTOR_DEBUG']
|
139
|
+
nil
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def connection_error
|
144
|
+
@cache[:connection_error]
|
145
|
+
end
|
146
|
+
|
147
|
+
def info
|
148
|
+
# Cache the info to avoid repeated queries
|
149
|
+
return @cache[:info] if @cache[:info]
|
150
|
+
|
151
|
+
# If we have a database and user but no connection, return basic info with error details
|
152
|
+
if connection.nil? && uri.path && uri.user
|
153
|
+
db_name = uri.path.sub(/^\//, '')
|
154
|
+
error_msg = connection_error ? " (#{connection_error[:type]})" : " (connection issue)"
|
155
|
+
|
156
|
+
@cache[:info] = {
|
157
|
+
'version' => "Unknown#{error_msg}",
|
158
|
+
'database' => db_name,
|
159
|
+
'user' => "#{uri.user}@remote"
|
160
|
+
}
|
161
|
+
return @cache[:info]
|
162
|
+
end
|
163
|
+
|
164
|
+
# Otherwise try to get info from connection
|
165
|
+
return nil unless connection
|
166
|
+
begin
|
167
|
+
result = execute_query("SELECT VERSION() AS version, DATABASE() AS `database`, USER() AS user")
|
168
|
+
@cache[:info] = result ? result.first : nil
|
169
|
+
return @cache[:info]
|
170
|
+
rescue Mysql2::Error => e
|
171
|
+
error_type = case
|
172
|
+
when e.error_number == 1226 then "max_user_connections exceeded"
|
173
|
+
else "query error"
|
174
|
+
end
|
175
|
+
|
176
|
+
puts "MariaDB info error: #{e.message} [#{error_type}]" if ENV['DETECTOR_DEBUG']
|
177
|
+
|
178
|
+
db_name = uri.path ? uri.path.sub(/^\//, '') : 'unknown'
|
179
|
+
{
|
180
|
+
'version' => "Unknown (#{error_type})",
|
181
|
+
'database' => db_name,
|
182
|
+
'user' => "#{uri.user}@error"
|
183
|
+
}
|
184
|
+
rescue => e
|
185
|
+
puts "General info error: #{e.message}" if ENV['DETECTOR_DEBUG']
|
186
|
+
nil
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
14
190
|
def version
|
191
|
+
# Cache the version to avoid repeated queries
|
192
|
+
return @cache[:version] if @cache[:version]
|
193
|
+
|
15
194
|
return nil unless info
|
16
|
-
|
195
|
+
begin
|
196
|
+
@cache[:version] = "MariaDB #{info['version']} on #{info['database']} (#{info['user']})"
|
197
|
+
return @cache[:version]
|
198
|
+
rescue => e
|
199
|
+
@cache[:version] = "MariaDB (connection error: #{e.message})"
|
200
|
+
return @cache[:version]
|
201
|
+
end
|
17
202
|
end
|
18
203
|
|
19
204
|
def databases
|
205
|
+
# Cache the databases to avoid repeated queries
|
206
|
+
return @cache[:databases] if @cache[:databases]
|
207
|
+
|
20
208
|
return [] unless connection
|
21
209
|
begin
|
22
|
-
#
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
210
|
+
# Get all databases at once to reduce connections
|
211
|
+
query = "SELECT
|
212
|
+
s.schema_name AS name,
|
213
|
+
IFNULL(FORMAT(SUM(t.data_length + t.index_length) / 1024 / 1024, 2), '0.00') AS size_mb,
|
214
|
+
IFNULL(SUM(t.data_length + t.index_length), 0) AS raw_size,
|
215
|
+
COUNT(t.table_name) AS table_count
|
216
|
+
FROM information_schema.SCHEMATA s
|
217
|
+
LEFT JOIN information_schema.TABLES t ON t.table_schema = s.schema_name
|
218
|
+
WHERE s.schema_name NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys')
|
219
|
+
GROUP BY s.schema_name
|
220
|
+
ORDER BY raw_size DESC"
|
221
|
+
|
222
|
+
result = execute_query(query)
|
28
223
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
224
|
+
if result
|
225
|
+
db_list = result.map do |row|
|
226
|
+
{
|
227
|
+
name: row['name'],
|
228
|
+
size: "#{row['size_mb']} MB",
|
229
|
+
raw_size: row['raw_size'].to_i,
|
230
|
+
table_count: row['table_count'].to_i
|
231
|
+
}
|
232
|
+
end
|
38
233
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
table_count: size_data['table_count'].to_i
|
45
|
-
}
|
234
|
+
# Sort by size
|
235
|
+
@cache[:databases] = db_list.sort_by { |db| -db[:raw_size] }
|
236
|
+
return @cache[:databases]
|
237
|
+
else
|
238
|
+
return []
|
46
239
|
end
|
47
|
-
|
48
|
-
# Sort by size
|
49
|
-
@databases = result.sort_by { |db| -db[:raw_size] }
|
50
240
|
rescue => e
|
51
241
|
puts "Error getting databases: #{e.message}"
|
52
242
|
[]
|
@@ -54,59 +244,175 @@ module Detector
|
|
54
244
|
end
|
55
245
|
|
56
246
|
def connection_info
|
57
|
-
|
247
|
+
# Cache connection info to avoid repeated queries
|
248
|
+
return @cache[:connection_info] if @cache[:connection_info]
|
249
|
+
|
250
|
+
# If no connection is available, provide error information
|
251
|
+
if connection.nil?
|
252
|
+
error_msg = connection_error ? connection_error[:type] : "unknown error"
|
253
|
+
|
254
|
+
@cache[:connection_info] = {
|
255
|
+
connection_count: { user: "ERROR", global: "ERROR" },
|
256
|
+
connection_limits: { user: "ERROR", global: "ERROR" },
|
257
|
+
error: "Connection error: #{error_msg}"
|
258
|
+
}
|
259
|
+
return @cache[:connection_info]
|
260
|
+
end
|
261
|
+
|
262
|
+
# If connection is available, get actual connection info
|
58
263
|
begin
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
264
|
+
user_limit_result = execute_query("SELECT @@max_user_connections AS `limit`")
|
265
|
+
user_count_result = execute_query("SELECT COUNT(*) AS count FROM information_schema.PROCESSLIST WHERE user = USER()")
|
266
|
+
global_limit_result = execute_query("SELECT @@max_connections AS `limit`")
|
267
|
+
global_count_result = execute_query("SELECT COUNT(*) AS count FROM information_schema.PROCESSLIST")
|
268
|
+
|
269
|
+
# Check if any query failed
|
270
|
+
if !user_limit_result || !user_count_result || !global_limit_result || !global_count_result
|
271
|
+
return {
|
272
|
+
connection_count: { user: "ERROR", global: "ERROR" },
|
273
|
+
connection_limits: { user: "ERROR", global: "ERROR" },
|
274
|
+
error: "Error executing connection info queries"
|
275
|
+
}
|
276
|
+
end
|
277
|
+
|
278
|
+
user_limit = user_limit_result.first['limit'].to_i
|
279
|
+
user_count = user_count_result.first['count'].to_i
|
280
|
+
global_limit = global_limit_result.first['limit'].to_i
|
281
|
+
global_count = global_count_result.first['count'].to_i
|
63
282
|
|
64
283
|
# If user limit is 0, it means no specific per-user limit (use global)
|
65
284
|
user_limit = global_limit if user_limit == 0
|
66
285
|
|
67
|
-
{
|
286
|
+
@cache[:connection_info] = {
|
68
287
|
connection_count: { user: user_count, global: global_count },
|
69
288
|
connection_limits: { user: user_limit, global: global_limit }
|
70
289
|
}
|
290
|
+
return @cache[:connection_info]
|
71
291
|
rescue Mysql2::Error => e
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
292
|
+
error_type = case
|
293
|
+
when e.error_number == 1226 then "max_user_connections exceeded"
|
294
|
+
else "query error"
|
295
|
+
end
|
296
|
+
|
297
|
+
puts "MariaDB connection_info error: #{e.message} [#{error_type}]" if ENV['DETECTOR_DEBUG']
|
298
|
+
|
299
|
+
@cache[:connection_info] = {
|
300
|
+
connection_count: { user: "ERROR", global: "ERROR" },
|
301
|
+
connection_limits: { user: "ERROR", global: "ERROR" },
|
302
|
+
error: "Connection error: #{error_type}"
|
303
|
+
}
|
304
|
+
return @cache[:connection_info]
|
305
|
+
rescue => e
|
306
|
+
puts "General connection_info error: #{e.message}" if ENV['DETECTOR_DEBUG']
|
307
|
+
nil
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
def database_count
|
312
|
+
# Cache database count to avoid repeated queries
|
313
|
+
return @cache[:database_count] if @cache[:database_count]
|
314
|
+
|
315
|
+
# If we have databases from cache, use the count
|
316
|
+
if @cache[:databases]
|
317
|
+
@cache[:database_count] = @cache[:databases].size
|
318
|
+
return @cache[:database_count]
|
319
|
+
end
|
320
|
+
|
321
|
+
# If no connection is available but we know the database name
|
322
|
+
if connection.nil? && uri.path
|
323
|
+
@cache[:database_count] = 1
|
324
|
+
return @cache[:database_count]
|
325
|
+
end
|
326
|
+
|
327
|
+
# Try to query the database
|
328
|
+
return nil unless connection
|
329
|
+
|
330
|
+
begin
|
331
|
+
result = execute_query("SELECT COUNT(*) AS count FROM information_schema.SCHEMATA WHERE schema_name NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys')")
|
332
|
+
|
333
|
+
if result && result.first
|
334
|
+
@cache[:database_count] = result.first['count']
|
335
|
+
return @cache[:database_count]
|
78
336
|
else
|
79
|
-
|
80
|
-
|
337
|
+
return 0
|
338
|
+
end
|
339
|
+
rescue Mysql2::Error => e
|
340
|
+
puts "Error getting database count: #{e.message} [#{e.error_number}]" if ENV['DETECTOR_DEBUG']
|
341
|
+
|
342
|
+
# If this is an authentication or permission error, return 0
|
343
|
+
if e.error_number == 1045 || e.error_number == 1044
|
344
|
+
return 0
|
345
|
+
end
|
346
|
+
|
347
|
+
# For max connections error, assume at least 1 database
|
348
|
+
if e.error_number == 1226 && uri.path
|
349
|
+
return 1
|
81
350
|
end
|
351
|
+
|
352
|
+
nil
|
82
353
|
rescue => e
|
83
|
-
puts "
|
354
|
+
puts "General error getting database count: #{e.message}" if ENV['DETECTOR_DEBUG']
|
84
355
|
nil
|
85
356
|
end
|
86
357
|
end
|
87
358
|
|
88
359
|
def tables(database_name)
|
360
|
+
# Cache tables to avoid repeated queries
|
361
|
+
@tables ||= {}
|
362
|
+
return @tables[database_name] if @tables[database_name]
|
363
|
+
|
364
|
+
# If no connection, try to provide at least some basic info from known values
|
365
|
+
if connection.nil? && uri.path && uri.path.sub(/^\//, '') == database_name
|
366
|
+
# We know this is the database in the URL, so return some hardcoded data
|
367
|
+
if @cache[:dummy_tables]
|
368
|
+
return @cache[:dummy_tables]
|
369
|
+
else
|
370
|
+
# No tables data available
|
371
|
+
return []
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
89
375
|
return [] unless connection
|
90
376
|
|
91
377
|
begin
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
378
|
+
result = execute_query("SELECT
|
379
|
+
table_name AS name,
|
380
|
+
IFNULL(FORMAT((data_length + index_length) / 1024 / 1024, 2), '0.00') AS size_mb,
|
381
|
+
IFNULL((data_length + index_length), 0) AS raw_size,
|
382
|
+
IFNULL(table_rows, 0) AS row_count
|
383
|
+
FROM information_schema.TABLES
|
384
|
+
WHERE table_schema = '#{database_name}'
|
385
|
+
ORDER BY raw_size DESC")
|
386
|
+
|
387
|
+
if result
|
388
|
+
@tables[database_name] = result.map do |row|
|
389
|
+
{
|
390
|
+
name: row['name'],
|
391
|
+
size: "#{row['size_mb']} MB",
|
392
|
+
raw_size: row['raw_size'].to_i,
|
393
|
+
row_count: row['row_count'].to_i
|
394
|
+
}
|
395
|
+
end
|
396
|
+
return @tables[database_name]
|
397
|
+
else
|
398
|
+
@tables[database_name] = []
|
399
|
+
return []
|
107
400
|
end
|
401
|
+
rescue Mysql2::Error => e
|
402
|
+
error_type = case
|
403
|
+
when e.error_number == 1044 then "access denied to information_schema"
|
404
|
+
when e.error_number == 1045 then "authentication failure"
|
405
|
+
when e.error_number == 1226 then "max_user_connections exceeded"
|
406
|
+
else "query error (#{e.error_number})"
|
407
|
+
end
|
408
|
+
|
409
|
+
puts "Error getting tables for #{database_name}: #{e.message} [#{error_type}]" if ENV['DETECTOR_DEBUG']
|
410
|
+
|
411
|
+
# Store the error for debugging
|
412
|
+
@tables[database_name] = []
|
413
|
+
return []
|
108
414
|
rescue => e
|
109
|
-
puts "
|
415
|
+
puts "General error getting tables for #{database_name}: #{e.message}" if ENV['DETECTOR_DEBUG']
|
110
416
|
[]
|
111
417
|
end
|
112
418
|
end
|
@@ -13,17 +13,29 @@ module Detector
|
|
13
13
|
|
14
14
|
def connection
|
15
15
|
# Create a new connection each time without caching
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
16
|
+
# Handle URI path correctly - strip leading slash if present
|
17
|
+
db_name = uri.path ? uri.path.sub(/^\//, '') : nil
|
18
|
+
|
19
|
+
begin
|
20
|
+
# Try connection with longer timeouts - fix for MariaDB syntax
|
21
|
+
Mysql2::Client.new(
|
22
|
+
host: host,
|
23
|
+
username: uri.user,
|
24
|
+
password: uri.password,
|
25
|
+
database: db_name,
|
26
|
+
port: port,
|
27
|
+
connect_timeout: 15,
|
28
|
+
read_timeout: 30,
|
29
|
+
write_timeout: 30,
|
30
|
+
init_command: "SET wait_timeout=900; SET interactive_timeout=900"
|
31
|
+
)
|
32
|
+
rescue Mysql2::Error => e
|
33
|
+
puts "MySQL connection error: #{e.message}" if ENV['DETECTOR_DEBUG']
|
34
|
+
nil
|
35
|
+
rescue => e
|
36
|
+
puts "General connection error: #{e.class} - #{e.message}" if ENV['DETECTOR_DEBUG']
|
37
|
+
nil
|
38
|
+
end
|
27
39
|
end
|
28
40
|
|
29
41
|
def info
|
data/lib/detector/version.rb
CHANGED
metadata
CHANGED
@@ -1,153 +1,152 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: detector
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.13
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Siegel
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: uri
|
15
14
|
requirement: !ruby/object:Gem::Requirement
|
16
15
|
requirements:
|
17
|
-
- - "
|
16
|
+
- - ">="
|
18
17
|
- !ruby/object:Gem::Version
|
19
18
|
version: 0.11.0
|
20
19
|
type: :runtime
|
21
20
|
prerelease: false
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
23
22
|
requirements:
|
24
|
-
- - "
|
23
|
+
- - ">="
|
25
24
|
- !ruby/object:Gem::Version
|
26
25
|
version: 0.11.0
|
27
26
|
- !ruby/object:Gem::Dependency
|
28
27
|
name: pg
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
30
29
|
requirements:
|
31
|
-
- - "
|
30
|
+
- - ">="
|
32
31
|
- !ruby/object:Gem::Version
|
33
32
|
version: '1.4'
|
34
33
|
type: :runtime
|
35
34
|
prerelease: false
|
36
35
|
version_requirements: !ruby/object:Gem::Requirement
|
37
36
|
requirements:
|
38
|
-
- - "
|
37
|
+
- - ">="
|
39
38
|
- !ruby/object:Gem::Version
|
40
39
|
version: '1.4'
|
41
40
|
- !ruby/object:Gem::Dependency
|
42
41
|
name: redis
|
43
42
|
requirement: !ruby/object:Gem::Requirement
|
44
43
|
requirements:
|
45
|
-
- - "
|
44
|
+
- - ">="
|
46
45
|
- !ruby/object:Gem::Version
|
47
46
|
version: '5.0'
|
48
47
|
type: :runtime
|
49
48
|
prerelease: false
|
50
49
|
version_requirements: !ruby/object:Gem::Requirement
|
51
50
|
requirements:
|
52
|
-
- - "
|
51
|
+
- - ">="
|
53
52
|
- !ruby/object:Gem::Version
|
54
53
|
version: '5.0'
|
55
54
|
- !ruby/object:Gem::Dependency
|
56
55
|
name: mysql2
|
57
56
|
requirement: !ruby/object:Gem::Requirement
|
58
57
|
requirements:
|
59
|
-
- - "
|
58
|
+
- - ">="
|
60
59
|
- !ruby/object:Gem::Version
|
61
60
|
version: '0.5'
|
62
61
|
type: :runtime
|
63
62
|
prerelease: false
|
64
63
|
version_requirements: !ruby/object:Gem::Requirement
|
65
64
|
requirements:
|
66
|
-
- - "
|
65
|
+
- - ">="
|
67
66
|
- !ruby/object:Gem::Version
|
68
67
|
version: '0.5'
|
69
68
|
- !ruby/object:Gem::Dependency
|
70
69
|
name: resolv
|
71
70
|
requirement: !ruby/object:Gem::Requirement
|
72
71
|
requirements:
|
73
|
-
- - "
|
72
|
+
- - ">="
|
74
73
|
- !ruby/object:Gem::Version
|
75
74
|
version: 0.2.1
|
76
75
|
type: :runtime
|
77
76
|
prerelease: false
|
78
77
|
version_requirements: !ruby/object:Gem::Requirement
|
79
78
|
requirements:
|
80
|
-
- - "
|
79
|
+
- - ">="
|
81
80
|
- !ruby/object:Gem::Version
|
82
81
|
version: 0.2.1
|
83
82
|
- !ruby/object:Gem::Dependency
|
84
83
|
name: bigdecimal
|
85
84
|
requirement: !ruby/object:Gem::Requirement
|
86
85
|
requirements:
|
87
|
-
- - "
|
86
|
+
- - ">="
|
88
87
|
- !ruby/object:Gem::Version
|
89
88
|
version: '3.1'
|
90
89
|
type: :runtime
|
91
90
|
prerelease: false
|
92
91
|
version_requirements: !ruby/object:Gem::Requirement
|
93
92
|
requirements:
|
94
|
-
- - "
|
93
|
+
- - ">="
|
95
94
|
- !ruby/object:Gem::Version
|
96
95
|
version: '3.1'
|
97
96
|
- !ruby/object:Gem::Dependency
|
98
97
|
name: net-smtp
|
99
98
|
requirement: !ruby/object:Gem::Requirement
|
100
99
|
requirements:
|
101
|
-
- - "
|
100
|
+
- - ">="
|
102
101
|
- !ruby/object:Gem::Version
|
103
102
|
version: 0.3.3
|
104
103
|
type: :runtime
|
105
104
|
prerelease: false
|
106
105
|
version_requirements: !ruby/object:Gem::Requirement
|
107
106
|
requirements:
|
108
|
-
- - "
|
107
|
+
- - ">="
|
109
108
|
- !ruby/object:Gem::Version
|
110
109
|
version: 0.3.3
|
111
110
|
- !ruby/object:Gem::Dependency
|
112
111
|
name: geocoder
|
113
112
|
requirement: !ruby/object:Gem::Requirement
|
114
113
|
requirements:
|
115
|
-
- - "
|
114
|
+
- - ">="
|
116
115
|
- !ruby/object:Gem::Version
|
117
116
|
version: '1.8'
|
118
117
|
type: :runtime
|
119
118
|
prerelease: false
|
120
119
|
version_requirements: !ruby/object:Gem::Requirement
|
121
120
|
requirements:
|
122
|
-
- - "
|
121
|
+
- - ">="
|
123
122
|
- !ruby/object:Gem::Version
|
124
123
|
version: '1.8'
|
125
124
|
- !ruby/object:Gem::Dependency
|
126
125
|
name: logger
|
127
126
|
requirement: !ruby/object:Gem::Requirement
|
128
127
|
requirements:
|
129
|
-
- - "
|
128
|
+
- - ">="
|
130
129
|
- !ruby/object:Gem::Version
|
131
130
|
version: '1.5'
|
132
131
|
type: :runtime
|
133
132
|
prerelease: false
|
134
133
|
version_requirements: !ruby/object:Gem::Requirement
|
135
134
|
requirements:
|
136
|
-
- - "
|
135
|
+
- - ">="
|
137
136
|
- !ruby/object:Gem::Version
|
138
137
|
version: '1.5'
|
139
138
|
- !ruby/object:Gem::Dependency
|
140
139
|
name: ostruct
|
141
140
|
requirement: !ruby/object:Gem::Requirement
|
142
141
|
requirements:
|
143
|
-
- - "
|
142
|
+
- - ">="
|
144
143
|
- !ruby/object:Gem::Version
|
145
144
|
version: '0.5'
|
146
145
|
type: :runtime
|
147
146
|
prerelease: false
|
148
147
|
version_requirements: !ruby/object:Gem::Requirement
|
149
148
|
requirements:
|
150
|
-
- - "
|
149
|
+
- - ">="
|
151
150
|
- !ruby/object:Gem::Version
|
152
151
|
version: '0.5'
|
153
152
|
- !ruby/object:Gem::Dependency
|
@@ -193,7 +192,6 @@ metadata:
|
|
193
192
|
homepage_uri: https://github.com/usiegj00/detector
|
194
193
|
source_code_uri: https://github.com/usiegj00/detector
|
195
194
|
changelog_uri: https://github.com/usiegj00/detector/blob/main/CHANGELOG.md
|
196
|
-
post_install_message:
|
197
195
|
rdoc_options: []
|
198
196
|
require_paths:
|
199
197
|
- lib
|
@@ -208,8 +206,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
208
206
|
- !ruby/object:Gem::Version
|
209
207
|
version: '0'
|
210
208
|
requirements: []
|
211
|
-
rubygems_version: 3.
|
212
|
-
signing_key:
|
209
|
+
rubygems_version: 3.6.7
|
213
210
|
specification_version: 4
|
214
211
|
summary: Detect and analyze various database systems
|
215
212
|
test_files: []
|