lib/dexter/indexer.rb in pgdexter-0.4.2 vs lib/dexter/indexer.rb in pgdexter-0.4.3

- old
+ new

@@ -12,10 +12,11 @@ @log_explain = options[:log_explain] @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 + @log_table = options[:log_table] @options = options @mutex = Mutex.new create_extension unless extension_exists? execute("SET lock_timeout = '5s'") @@ -26,11 +27,11 @@ log "Processing #{queries.size} new query fingerprints" process_queries(queries) end def stat_activity - execute <<-SQL + execute <<~SQL SELECT pid || ':' || COALESCE(query_start, xact_start) AS id, query, EXTRACT(EPOCH FROM NOW() - COALESCE(query_start, xact_start)) * 1000.0 AS duration_ms FROM @@ -42,10 +43,40 @@ ORDER BY 1 SQL end + # works with + # file_fdw: https://www.postgresql.org/docs/current/file-fdw.html + # log_fdw: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Appendix.PostgreSQL.CommonDBATasks.Extensions.foreign-data-wrappers.html + def csvlog_activity(last_log_time) + query = <<~SQL + SELECT + log_time, + message, + detail + FROM + #{conn.quote_ident(@log_table)} + WHERE + log_time >= \$1 + SQL + execute(query, params: [last_log_time]) + end + + # works with + # file_fdw: https://www.postgresql.org/docs/current/file-fdw.html + # log_fdw: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Appendix.PostgreSQL.CommonDBATasks.Extensions.foreign-data-wrappers.html + def stderr_activity + query = <<~SQL + SELECT + log_entry + FROM + #{conn.quote_ident(@log_table)} + SQL + execute(query) + end + def process_queries(queries) # reset hypothetical indexes reset_hypothetical_indexes tables = Set.new(database_tables + materialized_views) @@ -139,11 +170,11 @@ end def analyze_tables(tables) tables = tables.to_a.sort - analyze_stats = execute <<-SQL + analyze_stats = execute <<~SQL SELECT schemaname || '.' || relname AS table, last_analyze, last_autoanalyze FROM @@ -527,11 +558,11 @@ end rescue PG::ConnectionBad => e raise Dexter::Abort, e.message end - def execute(query, pretty: true) + def execute(query, pretty: true, params: []) # 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 @@ -539,11 +570,11 @@ # https://www.postgresql.org/docs/current/static/libpq-exec.html query = squish(query) if pretty log colorize("[sql] #{query}", :cyan) if @log_sql @mutex.synchronize do - conn.exec_params(query, []).to_a + conn.exec_params(query, params).to_a end end def plan(query) # strip semi-colons as another measure of defense @@ -563,11 +594,11 @@ def create_hypothetical_index(table, col_set) execute("SELECT * FROM hypopg_create_index('CREATE INDEX ON #{quote_ident(table)} (#{col_set.map { |c| quote_ident(c[:column]) }.join(", ")})')").first["indexname"] end def database_tables - result = execute <<-SQL + result = execute <<~SQL SELECT table_schema || '.' || table_name AS table_name FROM information_schema.tables WHERE @@ -576,11 +607,11 @@ result.map { |r| r["table_name"] } end def materialized_views if server_version_num >= 90300 - result = execute <<-SQL + result = execute <<~SQL SELECT schemaname || '.' || matviewname AS table_name FROM pg_matviews SQL @@ -593,11 +624,11 @@ def server_version_num execute("SHOW server_version_num").first["server_version_num"].to_i end def database_view_tables - result = execute <<-SQL + result = execute <<~SQL SELECT schemaname || '.' || viewname AS table_name, definition FROM pg_views @@ -619,11 +650,11 @@ view_tables end def stat_statements total_time = server_version_num >= 130000 ? "(total_plan_time + total_exec_time)" : "total_time" - result = execute <<-SQL + result = execute <<~SQL SELECT DISTINCT query FROM pg_stat_statements INNER JOIN @@ -665,11 +696,11 @@ def index_exists?(index) indexes([index[:table]]).find { |i| i["columns"] == index[:columns] } end def columns(tables) - columns = execute <<-SQL + columns = execute <<~SQL SELECT s.nspname || '.' || t.relname AS table_name, a.attname AS column_name, pg_catalog.format_type(a.atttypid, a.atttypmod) AS data_type FROM pg_attribute a @@ -684,11 +715,11 @@ columns.map { |v| {table: v["table_name"], column: v["column_name"], type: v["data_type"]} } end def indexes(tables) - execute(<<-SQL + query = <<~SQL SELECT schemaname || '.' || t.relname AS table, ix.relname AS name, regexp_replace(pg_get_indexdef(i.indexrelid), '^[^\\(]*\\((.*)\\)$', '\\1') AS columns, regexp_replace(pg_get_indexdef(i.indexrelid), '.* USING ([^ ]*) \\(.*', '\\1') AS using @@ -706,10 +737,10 @@ indexprs IS NULL AND indpred IS NULL ORDER BY 1, 2 SQL - ).map { |v| v["columns"] = v["columns"].sub(") WHERE (", " WHERE ").split(", ").map { |c| unquote(c) }; v } + execute(query).map { |v| v["columns"] = v["columns"].sub(") WHERE (", " WHERE ").split(", ").map { |c| unquote(c) }; v } end def search_path execute("SELECT current_schemas(true)")[0]["current_schemas"][1..-2].split(",") end