Skip to content

Commit

Permalink
Added support for views
Browse files Browse the repository at this point in the history
  • Loading branch information
ankane committed Feb 21, 2018
1 parent e808bcf commit 3590e23
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 6 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
@@ -1,6 +1,6 @@
## 0.3.3 [unreleased]

- Added support for materialized views
- Added support for views and materialized views
- Better handle case when multiple indexes are found for a query
- Added `--min-cost-savings-pct` option

Expand Down
40 changes: 39 additions & 1 deletion lib/dexter/indexer.rb
Expand Up @@ -38,11 +38,28 @@ def process_queries(queries)
no_schema_tables[group] = t2.sort_by { |t| [search_path_index[t.split(".")[0]] || 1000000, t] }[0]
end

# add tables from views
view_tables = database_view_tables
view_tables.each do |v, vt|
view_tables[v] = vt.map { |t| no_schema_tables[t] || t }
end

# fully resolve tables
# make sure no views in result
view_tables.each do |v, vt|
view_tables[v] = vt.flat_map { |t| view_tables[t] || [t] }.uniq
end

# filter queries from other databases and system tables
queries.each do |query|
# add schema to table if needed
query.tables = query.tables.map { |t| no_schema_tables[t] || t }

# substitute view tables
new_tables = query.tables.flat_map { |t| view_tables[t] || [t] }.uniq
query.tables_from_views = new_tables - query.tables
query.tables = new_tables

# check for missing tables
query.missing_tables = !query.tables.all? { |t| tables.include?(t) }
end
Expand Down Expand Up @@ -167,6 +184,7 @@ def create_hypothetical_indexes(queries, tables)

# filter tables for performance
tables = Set.new(explainable_queries.flat_map(&:tables))
tables_from_views = Set.new(explainable_queries.flat_map(&:tables_from_views))

if tables.any?
# since every set of multi-column indexes are expensive
Expand All @@ -183,7 +201,8 @@ def create_hypothetical_indexes(queries, tables)
end

# create hypothetical indexes
columns_by_table = columns(tables).select { |c| possible_columns.include?(c[:column]) }.group_by { |c| c[:table] }
# use all columns in tables from views
columns_by_table = columns(tables).select { |c| possible_columns.include?(c[:column]) || tables_from_views.include?(c[:table]) }.group_by { |c| c[:table] }

# create single column indexes
create_hypothetical_indexes_helper(columns_by_table, 1, candidates)
Expand Down Expand Up @@ -531,6 +550,25 @@ def materialized_views
result.map { |r| r["table_name"] }
end

def database_view_tables
result = execute <<-SQL
SELECT
schemaname || '.' || viewname AS table_name,
definition
FROM
pg_views
WHERE
schemaname NOT IN ('information_schema', 'pg_catalog')
SQL

view_tables = {}
result.each do |row|
view_tables[row["table_name"]] = PgQuery.parse(row["definition"]).tables
end

view_tables
end

def stat_statements
result = execute <<-SQL
SELECT
Expand Down
3 changes: 2 additions & 1 deletion lib/dexter/query.rb
Expand Up @@ -2,7 +2,7 @@ module Dexter
class Query
attr_reader :statement, :fingerprint, :plans
attr_writer :tables
attr_accessor :missing_tables, :new_cost, :total_time, :calls, :indexes, :suggest_index, :pass1_indexes, :pass2_indexes, :pass3_indexes, :candidate_tables
attr_accessor :missing_tables, :new_cost, :total_time, :calls, :indexes, :suggest_index, :pass1_indexes, :pass2_indexes, :pass3_indexes, :candidate_tables, :tables_from_views

def initialize(statement, fingerprint = nil)
@statement = statement
Expand All @@ -11,6 +11,7 @@ def initialize(statement, fingerprint = nil)
end
@fingerprint = fingerprint
@plans = []
@tables_from_views = []
end

def tables
Expand Down
3 changes: 1 addition & 2 deletions test/dexter_test.rb
Expand Up @@ -22,8 +22,7 @@ def test_delete
end

def test_view
# can't do views yet
assert_no_index "SELECT * FROM posts_view WHERE id = 1"
assert_index "SELECT * FROM posts_view WHERE view_id = 1", "public.posts (id)"
end

def test_materialized_view
Expand Down
2 changes: 1 addition & 1 deletion test/test_helper.rb
Expand Up @@ -18,7 +18,7 @@
hstore hstore
);
INSERT INTO posts (SELECT n AS id, n % 1000 AS blog_id, n % 10 AS user_id FROM generate_series(1, 100000) n);
CREATE VIEW posts_view AS SELECT * FROM posts;
CREATE VIEW posts_view AS SELECT id AS view_id FROM posts;
CREATE MATERIALIZED VIEW posts_materialized AS SELECT * FROM posts;
ANALYZE posts;
Expand Down

0 comments on commit 3590e23

Please sign in to comment.