lib/dexter/indexer.rb in pgdexter-0.3.4 vs lib/dexter/indexer.rb in pgdexter-0.3.5
- old
+ new
@@ -12,10 +12,11 @@
@min_time = options[:min_time] || 0
@min_calls = options[:min_calls] || 0
@analyze = options[:analyze]
@min_cost_savings_pct = options[:min_cost_savings_pct].to_i
@options = options
+ @mutex = Mutex.new
create_extension unless extension_exists?
execute("SET lock_timeout = '5s'")
end
@@ -179,11 +180,11 @@
end
begin
query.plans << plan(query.statement)
if @log_explain
# Pass format to prevent ANALYZE
- puts execute("EXPLAIN (FORMAT TEXT) #{safe_statement(query.statement)}").map { |r| r["QUERY PLAN"] }.join("\n")
+ puts execute("EXPLAIN (FORMAT TEXT) #{safe_statement(query.statement)}", pretty: false).map { |r| r["QUERY PLAN"] }.join("\n")
end
rescue PG::Error, JSON::NestingError => e
if @log_explain
log e.message
end
@@ -360,15 +361,16 @@
new_cost > 100 && winning_cost < new_cost * savings_ratio
else
winning_cost < query.initial_cost * savings_ratio
end
+ query_indexes = [winning_index]
+ new_cost3 = winning_cost
+ query.pass3_indexes = query_indexes
+
if use_winning
- query_indexes = [winning_index]
cost_savings3 = true
- new_cost3 = winning_cost
- query.pass3_indexes = query_indexes
else
suggest_index = false
end
end
@@ -454,11 +456,11 @@
log "Pass2: #{query.costs[2]} : #{log_indexes(query.pass2_indexes || [])}"
if query.costs[3]
log "Pass3: #{query.costs[3]} : #{log_indexes(query.pass3_indexes || [])}"
end
log "Final: #{query.new_cost} : #{log_indexes(query.suggest_index ? query_indexes : [])}"
- if query_indexes.size == 1 && !query.suggest_index
+ if (query.pass1_indexes.any? || query.pass2_indexes.any?) && !query.suggest_index
log "Need #{@min_cost_savings_pct}% cost savings to suggest index"
end
else
log "Could not run explain"
end
@@ -514,25 +516,28 @@
end
rescue PG::ConnectionBad => e
abort e.message
end
- def execute(query)
+ def execute(query, pretty: true)
# use exec_params instead of exec for security
#
# Unlike PQexec, PQexecParams allows at most one SQL command in the given string.
# (There can be semicolons in it, but not more than one nonempty command.)
# This is a limitation of the underlying protocol, but has some usefulness
# as an extra defense against SQL-injection attacks.
# https://www.postgresql.org/docs/current/static/libpq-exec.html
- query = squish(query)
+ query = squish(query) if pretty
log "SQL: #{query}" if @log_sql
- conn.exec_params(query, []).to_a
+
+ @mutex.synchronize do
+ conn.exec_params(query, []).to_a
+ end
end
def plan(query)
# strip semi-colons as another measure of defense
- JSON.parse(execute("EXPLAIN (FORMAT JSON) #{safe_statement(query)}").first["QUERY PLAN"], max_nesting: 1000).first["Plan"]
+ JSON.parse(execute("EXPLAIN (FORMAT JSON) #{safe_statement(query)}", pretty: false).first["QUERY PLAN"], max_nesting: 1000).first["Plan"]
end
# TODO for multicolumn indexes, use ordering
def create_hypothetical_indexes_helper(columns_by_table, n, candidates)
columns_by_table.each do |table, cols|