detector 0.4.0 → 0.7.0
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/bin/detector +34 -1
- data/lib/detector/addons/mariadb.rb +5 -0
- data/lib/detector/addons/mysql.rb +27 -0
- data/lib/detector/addons/postgres.rb +40 -0
- data/lib/detector/addons/redis.rb +47 -0
- data/lib/detector/base.rb +22 -0
- data/lib/detector/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 803cfd73b761e5b098dac4d15062b3a47e768d70d862c61301f3e8444c011762
|
4
|
+
data.tar.gz: f96d8885689baed71e8dfd8aa986ee24a6b0620c2745ff91a60e541d9324b781
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ec4621a88022efbac688c988275d21077dc260139a630659fef4f9257f4baa1409805add6f8056fe6c5a53b2329db849abc685839a5ace170b309089b9a909b3
|
7
|
+
data.tar.gz: ec584db3cb9ea42c2ce979c17cbc2d0facd7831d08105ab5ed1de0559c5ec5ca6e80fd32734851142ebb45eac676317fd8ac2f8fa72cd44eac41a5189c39bfe1
|
data/bin/detector
CHANGED
@@ -7,10 +7,23 @@ if ARGV.empty?
|
|
7
7
|
puts "Detector v#{Detector::VERSION}"
|
8
8
|
puts "Usage: detector <URI>"
|
9
9
|
puts "Example: detector \"postgres://user:pass@host:port/dbname\""
|
10
|
+
puts "Additional options:"
|
11
|
+
puts " --table=TABLE_NAME [--database=DB_NAME] : Estimate row count for a specific table"
|
10
12
|
exit 1
|
11
13
|
end
|
12
14
|
|
13
15
|
uri = ARGV[0]
|
16
|
+
options = {}
|
17
|
+
|
18
|
+
# Parse command-line options
|
19
|
+
ARGV[1..-1].each do |arg|
|
20
|
+
if arg.start_with?('--table=')
|
21
|
+
options[:table] = arg.split('=', 2)[1]
|
22
|
+
elsif arg.start_with?('--database=')
|
23
|
+
options[:database] = arg.split('=', 2)[1]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
14
27
|
detector = Detector.detect(uri)
|
15
28
|
|
16
29
|
if detector.nil?
|
@@ -18,11 +31,28 @@ if detector.nil?
|
|
18
31
|
exit 1
|
19
32
|
end
|
20
33
|
|
34
|
+
# If table is specified, show row count estimate and exit
|
35
|
+
if options[:table]
|
36
|
+
count = detector.estimated_row_count(table: options[:table], database: options[:database])
|
37
|
+
if count
|
38
|
+
puts "Estimated row count for #{options[:table]}: #{count}"
|
39
|
+
else
|
40
|
+
puts "Unable to estimate row count for #{options[:table]}"
|
41
|
+
end
|
42
|
+
detector.close
|
43
|
+
exit 0
|
44
|
+
end
|
45
|
+
|
21
46
|
puts "Detector v#{Detector::VERSION}"
|
22
47
|
puts "Detected: #{detector.kind}"
|
23
48
|
puts "Version: #{detector.version}"
|
24
49
|
puts "Host: #{detector.host}:#{detector.port}"
|
25
50
|
|
51
|
+
if detector.connection_count && detector.connection_limit
|
52
|
+
usage = detector.connection_usage_percentage
|
53
|
+
puts "Connections: #{detector.connection_count}/#{detector.connection_limit} (#{usage}%)"
|
54
|
+
end
|
55
|
+
|
26
56
|
if detector.respond_to?(:replication_available?) && !detector.replication_available?.nil?
|
27
57
|
puts "Replication: #{detector.replication_available? ? 'Available' : 'Not available'}"
|
28
58
|
end
|
@@ -71,4 +101,7 @@ if detector.databases?
|
|
71
101
|
end
|
72
102
|
end
|
73
103
|
end
|
74
|
-
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Make sure to close the connection
|
107
|
+
detector.close
|
@@ -108,6 +108,11 @@ module Detector
|
|
108
108
|
|
109
109
|
access_level
|
110
110
|
end
|
111
|
+
|
112
|
+
# MariaDB inherits the estimated_row_count method from MySQL, but we might want to override
|
113
|
+
# with MariaDB-specific optimizations or different statistics approaches in the future
|
114
|
+
|
115
|
+
# MariaDB inherits the close method from MySQL
|
111
116
|
end
|
112
117
|
end
|
113
118
|
|
@@ -187,6 +187,33 @@ module Detector
|
|
187
187
|
nil
|
188
188
|
end
|
189
189
|
end
|
190
|
+
|
191
|
+
def estimated_row_count(table:, database: nil)
|
192
|
+
return nil unless connection
|
193
|
+
|
194
|
+
# Use current database if none specified
|
195
|
+
db_name = database || info['database']
|
196
|
+
return nil unless db_name
|
197
|
+
|
198
|
+
begin
|
199
|
+
# Query information_schema.tables for the statistics-based row estimate
|
200
|
+
result = connection.query("SELECT table_rows AS estimate
|
201
|
+
FROM information_schema.tables
|
202
|
+
WHERE table_schema = '#{db_name}'
|
203
|
+
AND table_name = '#{table}'").first
|
204
|
+
|
205
|
+
result ? result['estimate'].to_i : nil
|
206
|
+
rescue => e
|
207
|
+
nil
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def close
|
212
|
+
if @conn
|
213
|
+
@conn.close rescue nil
|
214
|
+
@conn = nil
|
215
|
+
end
|
216
|
+
end
|
190
217
|
end
|
191
218
|
end
|
192
219
|
|
@@ -204,6 +204,46 @@ module Detector
|
|
204
204
|
nil
|
205
205
|
end
|
206
206
|
end
|
207
|
+
|
208
|
+
def estimated_row_count(table:, database: nil)
|
209
|
+
return nil unless connection
|
210
|
+
|
211
|
+
# Use the current database if none is specified
|
212
|
+
db_name = database || current_database
|
213
|
+
|
214
|
+
begin
|
215
|
+
# If we need to query a different database, temporarily connect to it
|
216
|
+
if db_name != current_database
|
217
|
+
# Create a temporary connection to the specified database
|
218
|
+
temp_conn = PG::Connection.new(host: host, port: port, user: uri.user,
|
219
|
+
password: uri.password, dbname: db_name) rescue nil
|
220
|
+
return nil unless temp_conn
|
221
|
+
|
222
|
+
# Use pg_class.reltuples for a fast, statistics-based row estimate
|
223
|
+
count = temp_conn.exec("SELECT reltuples::bigint AS estimate
|
224
|
+
FROM pg_class
|
225
|
+
WHERE relname = '#{table}'").first
|
226
|
+
temp_conn.close
|
227
|
+
return count ? count['estimate'].to_i : nil
|
228
|
+
end
|
229
|
+
|
230
|
+
# Query the current database using pg_class.reltuples
|
231
|
+
count = connection.exec("SELECT reltuples::bigint AS estimate
|
232
|
+
FROM pg_class
|
233
|
+
WHERE relname = '#{table}'").first
|
234
|
+
|
235
|
+
count ? count['estimate'].to_i : nil
|
236
|
+
rescue => e
|
237
|
+
nil
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def close
|
242
|
+
if @conn
|
243
|
+
@conn.close rescue nil
|
244
|
+
@conn = nil
|
245
|
+
end
|
246
|
+
end
|
207
247
|
end
|
208
248
|
end
|
209
249
|
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'redis'
|
2
|
+
require 'timeout'
|
2
3
|
|
3
4
|
module Detector
|
4
5
|
module Addons
|
@@ -171,6 +172,52 @@ module Detector
|
|
171
172
|
nil
|
172
173
|
end
|
173
174
|
end
|
175
|
+
|
176
|
+
def estimated_row_count(table:, database: nil)
|
177
|
+
return nil unless connection
|
178
|
+
|
179
|
+
# In Redis, the database is a number (0-15 typically) and "table" concept is closest to key patterns
|
180
|
+
# We'll interpret table parameter as a key pattern
|
181
|
+
|
182
|
+
begin
|
183
|
+
# Set the database if specified
|
184
|
+
if database
|
185
|
+
# Redis db numbers are integers
|
186
|
+
db_num = database.to_s.gsub(/[^0-9]/, '').to_i
|
187
|
+
connection.select(db_num) rescue nil
|
188
|
+
end
|
189
|
+
|
190
|
+
# Count keys matching the pattern (consider this a heuristic approximation)
|
191
|
+
# Use SCAN for larger datasets, as it doesn't block the server
|
192
|
+
count = 0
|
193
|
+
cursor = "0"
|
194
|
+
|
195
|
+
begin
|
196
|
+
# Timeout after a reasonable time to prevent long-running operations
|
197
|
+
Timeout.timeout(5) do
|
198
|
+
loop do
|
199
|
+
cursor, keys = connection.scan(cursor, match: table, count: 1000)
|
200
|
+
count += keys.size
|
201
|
+
break if cursor == "0"
|
202
|
+
end
|
203
|
+
end
|
204
|
+
rescue Timeout::Error
|
205
|
+
# If we time out, return the partial count with a note
|
206
|
+
return count
|
207
|
+
end
|
208
|
+
|
209
|
+
count
|
210
|
+
rescue => e
|
211
|
+
nil
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def close
|
216
|
+
if @conn
|
217
|
+
@conn.quit rescue nil
|
218
|
+
@conn = nil
|
219
|
+
end
|
220
|
+
end
|
174
221
|
end
|
175
222
|
end
|
176
223
|
|
data/lib/detector/base.rb
CHANGED
@@ -219,5 +219,27 @@ module Detector
|
|
219
219
|
def replication_available?
|
220
220
|
nil
|
221
221
|
end
|
222
|
+
|
223
|
+
def connection_count
|
224
|
+
nil
|
225
|
+
end
|
226
|
+
|
227
|
+
def connection_limit
|
228
|
+
nil
|
229
|
+
end
|
230
|
+
|
231
|
+
def connection_usage_percentage
|
232
|
+
return nil unless connection_count && connection_limit && connection_limit > 0
|
233
|
+
(connection_count.to_f / connection_limit.to_f * 100).round(1)
|
234
|
+
end
|
235
|
+
|
236
|
+
def estimated_row_count(table:, database: nil)
|
237
|
+
nil
|
238
|
+
end
|
239
|
+
|
240
|
+
def close
|
241
|
+
# Default implementation does nothing
|
242
|
+
# Subclasses should override to close any open connections
|
243
|
+
end
|
222
244
|
end
|
223
245
|
end
|
data/lib/detector/version.rb
CHANGED