pgdexter 0.5.1 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c68746f6134f6603c5549b561886b45ee3432df5d1e07e65a5907559901d333a
4
- data.tar.gz: 699c2744f3e2c9a79f8fa9ca6ae7a77586421243cbf7a1f92642f76724cc0cb0
3
+ metadata.gz: d40fb941685400a6a158502895da0d8b93d94db03da0ca4551714aea36e3dae3
4
+ data.tar.gz: 8b99de845f8d6f2e455e98c3505e689a0a55dac69f7c08ba6ebebabeeb1ddd63
5
5
  SHA512:
6
- metadata.gz: 343bc52539ef09541fd0774667034ea02fb88b0e4f820e52c86d77df46d059c253576c22272b91a407b764965f66c554278154da6ab923249ce9979ab3a5aed0
7
- data.tar.gz: a9b86931b4fc58d2b89c87f46f5008dfd47274cc39f096ec2b5143bea8858e29a786e387f9ccc29fe74475b5b0317e9d348d36f2a686cb19457ee79179a270f5
6
+ metadata.gz: b2b25419796e276074f5a49b2d109be72789ddf8a5c7c5cf29ba55900ac79c323e55b06bf5ab20dde4d34e4cf1e752e7362d23481d21d19a8b068a360562d13f
7
+ data.tar.gz: 382792e95461d718489d36c71e1ba01940f4b1d76de1b80527f0a4a19fcc90c215366645640970271011d5ad2c264051be57e62684e3278248d52bfaae983cd2
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## 0.5.3 (2024-03-05)
2
+
3
+ - Fixed error with hypothetical index limit
4
+ - Fixed error with foreign tables
5
+
6
+ ## 0.5.2 (2024-01-10)
7
+
8
+ - Added Docker image for `linux/arm64`
9
+ - Switched to `GENERIC_PLAN` for Postgres 16
10
+ - Fixed error with `auto_explain`
11
+ - Fixed warning with Ruby 3.3
12
+
1
13
  ## 0.5.1 (2023-05-27)
2
14
 
3
15
  - Fixed `JSON::NestingError`
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2017-2023 Andrew Kane
1
+ Copyright (c) 2017-2024 Andrew Kane
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -4,7 +4,7 @@ The automatic indexer for Postgres
4
4
 
5
5
  [Read about how it works](https://ankane.org/introducing-dexter) or [watch the talk](https://www.youtube.com/watch?v=Mni_1yTaNbE)
6
6
 
7
- [![Build Status](https://github.com/ankane/dexter/workflows/build/badge.svg?branch=master)](https://github.com/ankane/dexter/actions)
7
+ [![Build Status](https://github.com/ankane/dexter/actions/workflows/build.yml/badge.svg)](https://github.com/ankane/dexter/actions)
8
8
 
9
9
  ## Installation
10
10
 
@@ -96,10 +96,16 @@ module Dexter
96
96
  analyze_tables(tables) if tables.any? && (@analyze || @log_level == "debug2")
97
97
 
98
98
  # create hypothetical indexes and explain queries
99
- candidates = tables.any? ? create_hypothetical_indexes(queries.select(&:candidate_tables)) : {}
99
+ if tables.any?
100
+ # process in batches to prevent "hypopg: not more oid available" error
101
+ # https://hypopg.readthedocs.io/en/rel1_stable/usage.html#configuration
102
+ queries.select(&:candidate_tables).each_slice(500) do |batch|
103
+ create_hypothetical_indexes(batch)
104
+ end
105
+ end
100
106
 
101
107
  # see if new indexes were used and meet bar
102
- new_indexes = determine_indexes(queries, candidates, tables)
108
+ new_indexes = determine_indexes(queries, tables)
103
109
 
104
110
  # display and create new indexes
105
111
  show_and_create_indexes(new_indexes, queries)
@@ -185,7 +191,7 @@ module Dexter
185
191
 
186
192
  # get initial costs for queries
187
193
  calculate_plan(queries)
188
- explainable_queries = queries.select { |q| q.explainable? && q.high_cost? }
194
+ explainable_queries = queries.select { |q| q.plans.any? && q.high_cost? }
189
195
 
190
196
  # filter tables for performance
191
197
  tables = Set.new(explainable_queries.flat_map(&:tables))
@@ -228,7 +234,9 @@ module Dexter
228
234
  calculate_plan(explainable_queries)
229
235
  end
230
236
 
231
- candidates
237
+ queries.each do |query|
238
+ query.candidates = candidates
239
+ end
232
240
  end
233
241
 
234
242
  def find_columns(plan)
@@ -282,9 +290,8 @@ module Dexter
282
290
  query_indexes
283
291
  end
284
292
 
285
- def determine_indexes(queries, candidates, tables)
293
+ def determine_indexes(queries, tables)
286
294
  new_indexes = {}
287
- index_name_to_columns = candidates.invert
288
295
 
289
296
  # filter out existing indexes
290
297
  # this must happen at end of process
@@ -313,11 +320,11 @@ module Dexter
313
320
  cost_savings2 = new_cost > 100 && new_cost2 < new_cost * savings_ratio
314
321
 
315
322
  key = cost_savings2 ? 2 : 1
316
- query_indexes = hypo_indexes_from_plan(index_name_to_columns, query.plans[key], index_set)
323
+ query_indexes = hypo_indexes_from_plan(query.candidates, query.plans[key], index_set)
317
324
 
318
325
  # likely a bad suggestion, so try single column
319
326
  if cost_savings2 && query_indexes.size > 1
320
- query_indexes = hypo_indexes_from_plan(index_name_to_columns, query.plans[1], index_set)
327
+ query_indexes = hypo_indexes_from_plan(query.candidates, query.plans[1], index_set)
321
328
  cost_savings2 = false
322
329
  end
323
330
 
@@ -390,8 +397,8 @@ module Dexter
390
397
 
391
398
  # TODO optimize
392
399
  if @log_level.start_with?("debug")
393
- query.pass1_indexes = hypo_indexes_from_plan(index_name_to_columns, query.plans[1], index_set)
394
- query.pass2_indexes = hypo_indexes_from_plan(index_name_to_columns, query.plans[2], index_set)
400
+ query.pass1_indexes = hypo_indexes_from_plan(query.candidates, query.plans[1], index_set)
401
+ query.pass2_indexes = hypo_indexes_from_plan(query.candidates, query.plans[2], index_set)
395
402
  end
396
403
  end
397
404
  end
@@ -521,8 +528,8 @@ module Dexter
521
528
  raise Dexter::Abort, e.message
522
529
  end
523
530
 
524
- def execute(query, pretty: true, params: [])
525
- # use exec_params instead of exec for security
531
+ def execute(query, pretty: true, params: [], use_exec: false)
532
+ # use exec_params instead of exec when possible for security
526
533
  #
527
534
  # Unlike PQexec, PQexecParams allows at most one SQL command in the given string.
528
535
  # (There can be semicolons in it, but not more than one nonempty command.)
@@ -533,7 +540,11 @@ module Dexter
533
540
  log colorize("[sql] #{query}#{params.any? ? " /*#{params.to_json}*/" : ""}", :cyan) if @log_sql
534
541
 
535
542
  @mutex.synchronize do
536
- conn.exec_params("#{query} /*dexter*/", params).to_a
543
+ if use_exec
544
+ conn.exec("#{query} /*dexter*/").to_a
545
+ else
546
+ conn.exec_params("#{query} /*dexter*/", params).to_a
547
+ end
537
548
  end
538
549
  end
539
550
 
@@ -543,7 +554,9 @@ module Dexter
543
554
 
544
555
  # try to EXPLAIN normalized queries
545
556
  # https://dev.to/yugabyte/explain-from-pgstatstatements-normalized-queries-how-to-always-get-the-generic-plan-in--5cfi
546
- explain_normalized = query.include?("$1")
557
+ normalized = query.include?("$1")
558
+ generic_plan = normalized && server_version_num >= 160000
559
+ explain_normalized = normalized && !generic_plan
547
560
  if explain_normalized
548
561
  prepared_name = "dexter_prepared"
549
562
  execute("PREPARE #{prepared_name} AS #{safe_statement(query)}", pretty: false)
@@ -566,12 +579,14 @@ module Dexter
566
579
  end
567
580
  end
568
581
 
582
+ explain_prefix = generic_plan ? "GENERIC_PLAN, " : ""
583
+
569
584
  # strip semi-colons as another measure of defense
570
- plan = JSON.parse(execute("EXPLAIN (FORMAT JSON) #{safe_statement(query)}", pretty: false).first["QUERY PLAN"], max_nesting: 1000).first["Plan"]
585
+ plan = JSON.parse(execute("EXPLAIN (#{explain_prefix}FORMAT JSON) #{safe_statement(query)}", pretty: false, use_exec: generic_plan).first["QUERY PLAN"], max_nesting: 1000).first["Plan"]
571
586
 
572
587
  if @log_explain
573
588
  # Pass format to prevent ANALYZE
574
- puts execute("EXPLAIN (FORMAT TEXT) #{safe_statement(query)}", pretty: false).map { |r| r["QUERY PLAN"] }.join("\n")
589
+ puts execute("EXPLAIN (#{explain_prefix}FORMAT TEXT) #{safe_statement(query)}", pretty: false, use_exec: generic_plan).map { |r| r["QUERY PLAN"] }.join("\n")
575
590
  end
576
591
 
577
592
  plan
@@ -587,7 +602,8 @@ module Dexter
587
602
  columns_by_table.each do |table, cols|
588
603
  # no reason to use btree index for json columns
589
604
  cols.reject { |c| ["json", "jsonb"].include?(c[:type]) }.permutation(n) do |col_set|
590
- candidates[col_set] = create_hypothetical_index(table, col_set)
605
+ index_name = create_hypothetical_index(table, col_set)
606
+ candidates[index_name] = col_set
591
607
  end
592
608
  end
593
609
  end
@@ -604,6 +620,7 @@ module Dexter
604
620
  information_schema.tables
605
621
  WHERE
606
622
  table_catalog = current_database()
623
+ AND table_type IN ('BASE TABLE', 'VIEW')
607
624
  SQL
608
625
  result.map { |r| r["table_name"] }
609
626
  end
data/lib/dexter/query.rb CHANGED
@@ -2,7 +2,7 @@ module Dexter
2
2
  class Query
3
3
  attr_reader :statement, :fingerprint, :plans
4
4
  attr_writer :tables
5
- attr_accessor :missing_tables, :new_cost, :total_time, :calls, :indexes, :suggest_index, :pass1_indexes, :pass2_indexes, :pass3_indexes, :candidate_tables, :tables_from_views
5
+ attr_accessor :missing_tables, :new_cost, :total_time, :calls, :indexes, :suggest_index, :pass1_indexes, :pass2_indexes, :pass3_indexes, :candidate_tables, :tables_from_views, :candidates
6
6
 
7
7
  def initialize(statement, fingerprint = nil)
8
8
  @statement = statement
@@ -31,7 +31,7 @@ module Dexter
31
31
  end
32
32
 
33
33
  def explainable?
34
- plans.any?
34
+ plans.size >= 3
35
35
  end
36
36
 
37
37
  def costs
@@ -1,3 +1,3 @@
1
1
  module Dexter
2
- VERSION = "0.5.1"
2
+ VERSION = "0.5.3"
3
3
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pgdexter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-05-27 00:00:00.000000000 Z
11
+ date: 2024-03-06 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: csv
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: pg
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -96,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
96
110
  - !ruby/object:Gem::Version
97
111
  version: '0'
98
112
  requirements: []
99
- rubygems_version: 3.4.10
113
+ rubygems_version: 3.5.3
100
114
  signing_key:
101
115
  specification_version: 4
102
116
  summary: The automatic indexer for Postgres