pgdexter 0.6.0 → 0.6.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 234a20833730445c80e03b5d789f174f9f50e9fb443614938912aaa1f74a7bd8
4
- data.tar.gz: b286dd2c8735f17cd943c71405a2863699d4540487f5f1c730f5c19e6337da79
3
+ metadata.gz: 8c6d5363b46c95785cf87964a0d1226161b43433e10d7700c71342ebcf3c10bc
4
+ data.tar.gz: 89edef30213d5ec288c9c7ab8bcb8b371649f569408a34abb4fe9038050729dc
5
5
  SHA512:
6
- metadata.gz: 6464fe3a0d24567a1759272ca8ab82b6462c88c5221f10a7784a0fdf41234268896b0368fbd20e442ce8f7a307d917c6779f484006c1c4f7971be07a8ba26de1
7
- data.tar.gz: e3b0e7abc058f8f250e431d8b332e761a83bbc737b782449804a663f251dd7f33dc3213cc247dfb6be06b636c1d166af54803c13d7541dd9a8479baecaa86e8c
6
+ metadata.gz: d65547dc4e985162671f3a5d32b66534ad9ceb7e17dbe1bbf516e16b0b820abadbf32aa045a0a431b36e92ea5ab1347539db220bc08d67c50095b501c6b56b62
7
+ data.tar.gz: c86be55126a037ddeba3170076ac94b3d10cbe0f29e530aa5a2e077b62719998427668420c3802f2ae612cea4127f4536db16683466c8f9184678ec2284481f5
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 0.6.2 (2025-07-27)
2
+
3
+ - Fixed error with utility statements
4
+
5
+ ## 0.6.1 (2025-06-08)
6
+
7
+ - Fixed error with column types without `btree` support
8
+
1
9
  ## 0.6.0 (2025-06-01)
2
10
 
3
11
  - Added Linux packages for Ubuntu 24.04 and Debian 12
data/README.md CHANGED
@@ -12,8 +12,8 @@ First, install [HypoPG](https://github.com/HypoPG/hypopg) on your database serve
12
12
 
13
13
  ```sh
14
14
  cd /tmp
15
- curl -L https://github.com/HypoPG/hypopg/archive/1.4.1.tar.gz | tar xz
16
- cd hypopg-1.4.1
15
+ curl -L https://github.com/HypoPG/hypopg/archive/1.4.2.tar.gz | tar xz
16
+ cd hypopg-1.4.2
17
17
  make
18
18
  make install # may need sudo
19
19
  ```
@@ -232,10 +232,10 @@ If compilation fails with `fatal error: postgres.h: No such file or directory`,
232
232
  For Ubuntu and Debian, use:
233
233
 
234
234
  ```sh
235
- sudo apt-get install postgresql-server-dev-16
235
+ sudo apt-get install postgresql-server-dev-17
236
236
  ```
237
237
 
238
- Note: Replace `16` with your Postgres server version
238
+ Note: Replace `17` with your Postgres server version
239
239
 
240
240
  ## Additional Installation Methods
241
241
 
data/lib/dexter/client.rb CHANGED
@@ -112,7 +112,7 @@ module Dexter
112
112
 
113
113
  options[:dbname] = arguments.shift unless options[:dbname]
114
114
 
115
- # TODO don't use global var
115
+ # TODO remove global variable
116
116
  $log_level = options[:log_level].to_s.downcase
117
117
  unless ["error", "info", "debug", "debug2", "debug3"].include?($log_level)
118
118
  raise Error, "Unknown log level"
@@ -41,6 +41,10 @@ module Dexter
41
41
  end
42
42
  end
43
43
 
44
+ def quote_ident(value)
45
+ value.split(".").map { |v| conn.quote_ident(v) }.join(".")
46
+ end
47
+
44
48
  def server_version_num
45
49
  @server_version_num ||= execute("SHOW server_version_num").first["server_version_num"].to_i
46
50
  end
@@ -0,0 +1,72 @@
1
+ module Dexter
2
+ class IndexCreator
3
+ include Logging
4
+
5
+ def initialize(connection, indexer, new_indexes, tablespace)
6
+ @connection = connection
7
+ @indexer = indexer
8
+ @new_indexes = new_indexes
9
+ @tablespace = tablespace
10
+ end
11
+
12
+ # 1. create lock
13
+ # 2. refresh existing index list
14
+ # 3. create indexes that still don't exist
15
+ # 4. release lock
16
+ def perform
17
+ with_advisory_lock do
18
+ @new_indexes.each do |index|
19
+ unless index_exists?(index)
20
+ statement = String.new("CREATE INDEX CONCURRENTLY ON #{@connection.quote_ident(index[:table])} (#{index[:columns].map { |c| @connection.quote_ident(c) }.join(", ")})")
21
+ statement << " TABLESPACE #{@connection.quote_ident(@tablespace)}" if @tablespace
22
+ log "Creating index: #{statement}"
23
+ started_at = monotonic_time
24
+ begin
25
+ @connection.execute(statement)
26
+ log "Index created: #{((monotonic_time - started_at) * 1000).to_i} ms"
27
+ rescue PG::LockNotAvailable
28
+ log "Could not acquire lock: #{index[:table]}"
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def monotonic_time
38
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
39
+ end
40
+
41
+ def with_advisory_lock
42
+ lock_id = 123456
43
+ first_time = true
44
+ while @connection.execute("SELECT pg_try_advisory_lock($1)", params: [lock_id]).first["pg_try_advisory_lock"] != "t"
45
+ if first_time
46
+ log "Waiting for lock..."
47
+ first_time = false
48
+ end
49
+ sleep(1)
50
+ end
51
+ yield
52
+ ensure
53
+ suppress_messages do
54
+ @connection.execute("SELECT pg_advisory_unlock($1)", params: [lock_id])
55
+ end
56
+ end
57
+
58
+ def suppress_messages
59
+ @connection.send(:conn).set_notice_processor do |message|
60
+ # do nothing
61
+ end
62
+ yield
63
+ ensure
64
+ # clear notice processor
65
+ @connection.send(:conn).set_notice_processor
66
+ end
67
+
68
+ def index_exists?(index)
69
+ @indexer.send(:indexes, [index[:table]]).find { |i| i["columns"] == index[:columns] }
70
+ end
71
+ end
72
+ end
@@ -30,6 +30,7 @@ module Dexter
30
30
 
31
31
  if tables.any?
32
32
  # analyze tables if needed
33
+ # TODO remove @log_level in 0.7.0
33
34
  analyze_tables(tables) if @analyze || @log_level == "debug2"
34
35
 
35
36
  # get initial costs for queries
@@ -41,7 +42,8 @@ module Dexter
41
42
  ColumnResolver.new(@connection, candidate_queries, log_level: @log_level).perform
42
43
  candidate_queries.each do |query|
43
44
  # no reason to use btree index for json columns
44
- query.candidate_columns = query.columns.reject { |c| ["json", "jsonb"].include?(c[:type]) }.sort_by { |c| [c[:table], c[:column]] }
45
+ # TODO check type supports btree
46
+ query.candidate_columns = query.columns.reject { |c| ["json", "jsonb", "point"].include?(c[:type]) }.sort_by { |c| [c[:table], c[:column]] }
45
47
  end
46
48
  candidate_queries.select! { |q| q.candidate_columns.any? }
47
49
 
@@ -59,7 +61,7 @@ module Dexter
59
61
  show_debug_info(new_indexes, queries) if @log_level.start_with?("debug")
60
62
 
61
63
  # create new indexes
62
- create_indexes(new_indexes) if @create && new_indexes.any?
64
+ IndexCreator.new(@connection, self, new_indexes, @tablespace).perform if @create && new_indexes.any?
63
65
  end
64
66
 
65
67
  private
@@ -120,7 +122,7 @@ module Dexter
120
122
  end
121
123
 
122
124
  if @analyze && (!la || la < Time.now - 3600)
123
- statement = "ANALYZE #{quote_ident(table)}"
125
+ statement = "ANALYZE #{@connection.quote_ident(table)}"
124
126
  log "Running analyze: #{statement}"
125
127
  execute(statement)
126
128
  end
@@ -134,7 +136,8 @@ module Dexter
134
136
  puts
135
137
  end
136
138
  begin
137
- query.plans << plan(query.statement)
139
+ plan = self.plan(query.statement)
140
+ query.plans << plan if plan && plan["Total Cost"]
138
141
  rescue PG::Error, JSON::NestingError => e
139
142
  if @log_explain
140
143
  log e.message
@@ -183,8 +186,12 @@ module Dexter
183
186
 
184
187
  def create_candidate_indexes(candidate_indexes, index_mapping)
185
188
  candidate_indexes.each do |columns|
186
- index_name = create_hypothetical_index(columns[0][:table], columns.map { |c| c[:column] })
187
- index_mapping[index_name] = columns
189
+ begin
190
+ index_name = create_hypothetical_index(columns[0][:table], columns.map { |c| c[:column] })
191
+ index_mapping[index_name] = columns
192
+ rescue PG::UndefinedObject
193
+ # data type x has no default operator class for access method "btree"
194
+ end
188
195
  end
189
196
  rescue PG::InternalError
190
197
  # hypopg: not more oid available
@@ -446,37 +453,6 @@ module Dexter
446
453
  end
447
454
  end
448
455
 
449
- # 1. create lock
450
- # 2. refresh existing index list
451
- # 3. create indexes that still don't exist
452
- # 4. release lock
453
- def create_indexes(new_indexes)
454
- with_advisory_lock do
455
- new_indexes.each do |index|
456
- unless index_exists?(index)
457
- statement = String.new("CREATE INDEX CONCURRENTLY ON #{quote_ident(index[:table])} (#{index[:columns].map { |c| quote_ident(c) }.join(", ")})")
458
- statement << " TABLESPACE #{quote_ident(@tablespace)}" if @tablespace
459
- log "Creating index: #{statement}"
460
- started_at = monotonic_time
461
- begin
462
- execute(statement)
463
- log "Index created: #{((monotonic_time - started_at) * 1000).to_i} ms"
464
- rescue PG::LockNotAvailable
465
- log "Could not acquire lock: #{index[:table]}"
466
- end
467
- end
468
- end
469
- end
470
- end
471
-
472
- def monotonic_time
473
- Process.clock_gettime(Process::CLOCK_MONOTONIC)
474
- end
475
-
476
- def conn
477
- @connection.send(:conn)
478
- end
479
-
480
456
  def execute(...)
481
457
  @connection.execute(...)
482
458
  end
@@ -522,38 +498,7 @@ module Dexter
522
498
  end
523
499
 
524
500
  def create_hypothetical_index(table, columns)
525
- execute("SELECT * FROM hypopg_create_index('CREATE INDEX ON #{quote_ident(table)} (#{columns.map { |c| quote_ident(c) }.join(", ")})')").first["indexname"]
526
- end
527
-
528
- def with_advisory_lock
529
- lock_id = 123456
530
- first_time = true
531
- while execute("SELECT pg_try_advisory_lock($1)", params: [lock_id]).first["pg_try_advisory_lock"] != "t"
532
- if first_time
533
- log "Waiting for lock..."
534
- first_time = false
535
- end
536
- sleep(1)
537
- end
538
- yield
539
- ensure
540
- suppress_messages do
541
- execute("SELECT pg_advisory_unlock($1)", params: [lock_id])
542
- end
543
- end
544
-
545
- def suppress_messages
546
- conn.set_notice_processor do |message|
547
- # do nothing
548
- end
549
- yield
550
- ensure
551
- # clear notice processor
552
- conn.set_notice_processor
553
- end
554
-
555
- def index_exists?(index)
556
- indexes([index[:table]]).find { |i| i["columns"] == index[:columns] }
501
+ execute("SELECT * FROM hypopg_create_index('CREATE INDEX ON #{@connection.quote_ident(table)} (#{columns.map { |c| @connection.quote_ident(c) }.join(", ")})')").first["indexname"]
557
502
  end
558
503
 
559
504
  def indexes(tables)
@@ -590,10 +535,6 @@ module Dexter
590
535
  end
591
536
  end
592
537
 
593
- def quote_ident(value)
594
- value.split(".").map { |v| conn.quote_ident(v) }.join(".")
595
- end
596
-
597
538
  def safe_statement(statement)
598
539
  statement.gsub(";", "")
599
540
  end
@@ -8,7 +8,7 @@ module Dexter
8
8
  }
9
9
 
10
10
  def output
11
- $stdout
11
+ $dexter_output || $stdout
12
12
  end
13
13
 
14
14
  def log(message = "")
@@ -1,6 +1,6 @@
1
1
  module Dexter
2
2
  class StderrLogParser < LogParser
3
- LINE_SEPERATOR = ": ".freeze
3
+ LINE_SEPARATOR = ": ".freeze
4
4
  DETAIL_LINE = "DETAIL: ".freeze
5
5
 
6
6
  def perform(collector)
@@ -11,7 +11,7 @@ module Dexter
11
11
  if active_line
12
12
  if line.include?(DETAIL_LINE)
13
13
  add_parameters(active_line, line.chomp.split(DETAIL_LINE)[1])
14
- elsif line.include?(LINE_SEPERATOR)
14
+ elsif line.include?(LINE_SEPARATOR)
15
15
  collector.add(active_line, duration)
16
16
  active_line = nil
17
17
  else
@@ -1,3 +1,3 @@
1
1
  module Dexter
2
- VERSION = "0.6.0"
2
+ VERSION = "0.6.2"
3
3
  end
data/lib/dexter.rb CHANGED
@@ -15,6 +15,7 @@ require_relative "dexter/client"
15
15
  require_relative "dexter/collector"
16
16
  require_relative "dexter/column_resolver"
17
17
  require_relative "dexter/connection"
18
+ require_relative "dexter/index_creator"
18
19
  require_relative "dexter/indexer"
19
20
  require_relative "dexter/processor"
20
21
  require_relative "dexter/query"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pgdexter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
@@ -80,6 +80,7 @@ files:
80
80
  - lib/dexter/collector.rb
81
81
  - lib/dexter/column_resolver.rb
82
82
  - lib/dexter/connection.rb
83
+ - lib/dexter/index_creator.rb
83
84
  - lib/dexter/indexer.rb
84
85
  - lib/dexter/logging.rb
85
86
  - lib/dexter/parsers/csv_log_parser.rb
@@ -114,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
114
115
  - !ruby/object:Gem::Version
115
116
  version: '0'
116
117
  requirements: []
117
- rubygems_version: 3.6.7
118
+ rubygems_version: 3.6.9
118
119
  specification_version: 4
119
120
  summary: The automatic indexer for Postgres
120
121
  test_files: []