neo4j_bolt 0.4.1 → 0.4.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 +4 -4
- data/README.md +1 -1
- data/lib/neo4j_bolt/version.rb +1 -1
- data/lib/neo4j_bolt.rb +144 -27
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 66da85ac04ffd0ef43fea4fba9a676ea2168770f85825573652a58dbc0a48f2b
|
|
4
|
+
data.tar.gz: 132911b813e88d5a3efb590fe5cd29da0c8adb6663b06b9931bb0a03f4fcae9c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 37e9205800f8661683bc246c6a962a15254c2d1e4a33d5b948f85d5e59d9ac38911e67c4a2e18bb318bc9f3ceeb3eedb7199004e98b0ce1694ae2e0f16f9ce8e
|
|
7
|
+
data.tar.gz: 3f7066485f251b5939b9a448b441290f16714311ebd7baa2b2baa27d785ae2463902c63b6c14b532df5adfe574242c8e415305967677110d92943a431e4360d3
|
data/README.md
CHANGED
|
@@ -157,7 +157,7 @@ File.open("database.dump", "r") do |io|
|
|
|
157
157
|
end
|
|
158
158
|
```
|
|
159
159
|
|
|
160
|
-
Passing `progress_io:` is optional for the Ruby API. The CLI always reports dump/load progress on stderr, so dump data written to stdout remains safe to redirect or pipe.
|
|
160
|
+
Passing `progress_io:` is optional for the Ruby API. The CLI always reports dump/load progress on stderr, so dump data written to stdout remains safe to redirect or pipe. In `auto` mode a terminal gets a colored, in-place progress bar while redirected stderr receives periodic plain-text progress lines. Set `NEO4J_BOLT_PROGRESS=pretty` to force the terminal display through a container or other wrapper that hides the TTY, or `NEO4J_BOLT_PROGRESS=plain` to force log-friendly output. `NO_COLOR` disables colors without disabling the in-place display.
|
|
161
161
|
|
|
162
162
|
The `0, 1, 2, ...` IDs are synthetic dump-local IDs. Database-internal IDs and element IDs are never written to the persistent format. Dumping keeps the count, node, and relationship reads in one transaction, so the driver-provided entity identity used to connect the two streamed result sets stays within Neo4j's transaction-scoped identity guarantee. To keep dump numbering deterministic, ordering uses `elementId()` on modern Neo4j and an internal `id()` fallback only on Neo4j 4.4, where `elementId()` does not exist. Old Neo4jBolt dumps remain loadable.
|
|
163
163
|
|
data/lib/neo4j_bolt/version.rb
CHANGED
data/lib/neo4j_bolt.rb
CHANGED
|
@@ -23,70 +23,185 @@ module Neo4jBolt
|
|
|
23
23
|
|
|
24
24
|
class ProgressReporter
|
|
25
25
|
UPDATE_INTERVAL = 0.5
|
|
26
|
+
BAR_WIDTH = 24
|
|
27
|
+
SPINNER = %w[⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏].freeze
|
|
28
|
+
ANSI = {
|
|
29
|
+
reset: "\e[0m",
|
|
30
|
+
bold: "\e[1m",
|
|
31
|
+
dim: "\e[2m",
|
|
32
|
+
cyan: "\e[36m",
|
|
33
|
+
green: "\e[32m",
|
|
34
|
+
yellow: "\e[33m"
|
|
35
|
+
}.freeze
|
|
26
36
|
|
|
27
37
|
def initialize(io)
|
|
28
38
|
@io = io
|
|
29
|
-
|
|
39
|
+
mode = ENV.fetch("NEO4J_BOLT_PROGRESS", "auto")
|
|
40
|
+
unless %w[auto pretty plain].include?(mode)
|
|
41
|
+
raise ArgumentError, "NEO4J_BOLT_PROGRESS must be auto, pretty, or plain"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
detected_tty = io && io.respond_to?(:tty?) && io.tty? && Neo4jBolt.bolt_verbosity.to_i.zero?
|
|
45
|
+
@pretty = io && (mode == "pretty" || (mode == "auto" && detected_tty))
|
|
46
|
+
@color = @pretty && !ENV.key?("NO_COLOR") && (mode == "pretty" || ENV["TERM"] != "dumb")
|
|
30
47
|
@last_update_at = nil
|
|
31
|
-
@line_width = 0
|
|
32
48
|
@line_open = false
|
|
49
|
+
@spinner_index = 0
|
|
33
50
|
end
|
|
34
51
|
|
|
35
|
-
def update(message, force: false)
|
|
52
|
+
def update(message, force: false, percent: nil)
|
|
36
53
|
return unless @io
|
|
37
54
|
|
|
38
55
|
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
39
56
|
return if !force && @last_update_at && now - @last_update_at < UPDATE_INTERVAL
|
|
40
57
|
|
|
41
|
-
if @
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
@io.flush
|
|
45
|
-
@line_width = [@line_width, message.length].max
|
|
46
|
-
@line_open = true
|
|
58
|
+
if @pretty
|
|
59
|
+
percent ||= extract_percent(message)
|
|
60
|
+
render_line(activity_line(message, percent))
|
|
47
61
|
else
|
|
48
62
|
@io.puts message
|
|
49
63
|
end
|
|
50
64
|
@last_update_at = now
|
|
51
65
|
end
|
|
52
66
|
|
|
53
|
-
def note(message)
|
|
67
|
+
def note(message, level: :info)
|
|
54
68
|
return unless @io
|
|
55
69
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
70
|
+
if @pretty && level == :info
|
|
71
|
+
update(message, force: true)
|
|
72
|
+
else
|
|
73
|
+
clear_line
|
|
74
|
+
if @pretty
|
|
75
|
+
icon = level == :warning ? color(:yellow, "!") : color(:cyan, "•")
|
|
76
|
+
@io.puts "#{icon} #{terminal_text(message)}"
|
|
77
|
+
else
|
|
78
|
+
@io.puts message
|
|
79
|
+
end
|
|
80
|
+
@last_update_at = nil
|
|
81
|
+
end
|
|
59
82
|
end
|
|
60
83
|
|
|
61
84
|
def finish(message)
|
|
62
85
|
return unless @io
|
|
63
86
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
87
|
+
if @pretty
|
|
88
|
+
clear_line
|
|
89
|
+
label, detail = terminal_parts(message)
|
|
90
|
+
line = "#{color(:green, '✓')} #{color(:bold, label)}"
|
|
91
|
+
line += " #{detail}" unless detail.empty?
|
|
92
|
+
@io.puts line
|
|
68
93
|
@line_open = false
|
|
94
|
+
else
|
|
95
|
+
@io.puts message
|
|
69
96
|
end
|
|
97
|
+
@last_update_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
70
98
|
end
|
|
71
99
|
|
|
72
100
|
def close
|
|
73
|
-
return unless @io && @
|
|
101
|
+
return unless @io && @pretty && @line_open
|
|
74
102
|
|
|
75
|
-
|
|
76
|
-
@line_width = 0
|
|
77
|
-
@line_open = false
|
|
103
|
+
clear_line
|
|
78
104
|
end
|
|
79
105
|
|
|
80
106
|
private
|
|
81
107
|
|
|
82
|
-
def
|
|
83
|
-
|
|
108
|
+
def activity_line(message, percent)
|
|
109
|
+
label, detail = terminal_parts(message, strip_percent: !percent.nil?)
|
|
110
|
+
spinner = SPINNER[@spinner_index % SPINNER.length]
|
|
111
|
+
@spinner_index += 1
|
|
112
|
+
|
|
113
|
+
parts = [color(:cyan, spinner), color(:bold, label)]
|
|
114
|
+
if percent
|
|
115
|
+
percent = [[percent.to_i, 0].max, 100].min
|
|
116
|
+
parts << progress_bar(percent)
|
|
117
|
+
parts << color(:bold, format("%3d%%", percent))
|
|
118
|
+
end
|
|
119
|
+
parts << detail unless detail.empty?
|
|
120
|
+
parts.join(" ")
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def progress_bar(percent)
|
|
124
|
+
filled = (percent * BAR_WIDTH / 100.0).round
|
|
125
|
+
empty = BAR_WIDTH - filled
|
|
126
|
+
"#{color(:cyan, '━' * filled)}#{color(:dim, '─' * empty)}"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def terminal_parts(message, strip_percent: false)
|
|
130
|
+
text = message.sub(/\.\.\.\z/, "")
|
|
131
|
+
label, detail = text.split(": ", 2)
|
|
132
|
+
label = pretty_label(label)
|
|
133
|
+
detail ||= ""
|
|
134
|
+
if strip_percent
|
|
135
|
+
detail = detail.sub(/\s+\(\d+%\)/, "")
|
|
136
|
+
detail = "" if detail.match?(/\A\d+%\z/)
|
|
137
|
+
end
|
|
138
|
+
detail = detail.gsub(/ \[batch (\d+)\]/) do
|
|
139
|
+
" #{color(:dim, '·')} batch #{format_count(Regexp.last_match(1))}"
|
|
140
|
+
end
|
|
141
|
+
detail = format_counts(detail)
|
|
142
|
+
detail = detail.gsub(", ", " #{color(:dim, '·')} ")
|
|
143
|
+
[label, detail]
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def terminal_text(message)
|
|
147
|
+
label, detail = terminal_parts(message)
|
|
148
|
+
detail.empty? ? format_counts(label) : "#{label}: #{detail}"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def pretty_label(label)
|
|
152
|
+
case label
|
|
153
|
+
when "Building temporary relationship lookup index"
|
|
154
|
+
"Building lookup index"
|
|
155
|
+
when "Temporary relationship lookup index ready."
|
|
156
|
+
"Lookup index ready"
|
|
157
|
+
when "Cleaning temporary load metadata"
|
|
158
|
+
"Cleaning load metadata"
|
|
159
|
+
when "Temporary load metadata removed."
|
|
160
|
+
"Load metadata removed"
|
|
161
|
+
else
|
|
162
|
+
label
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def extract_percent(message)
|
|
167
|
+
if (match = message.match(/\((\d+)%\)/)) || (match = message.match(/:\s*(\d+)%\z/))
|
|
168
|
+
return match[1].to_i
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
match = message.match(/:\s*(\d+)\/(\d+) nodes\z/)
|
|
172
|
+
return nil unless match
|
|
173
|
+
|
|
174
|
+
total = match[2].to_i
|
|
175
|
+
total.zero? ? 100 : match[1].to_i * 100 / total
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def format_counts(text)
|
|
179
|
+
text.gsub(/\b\d{4,}\b/) { |number| format_count(number) }
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def format_count(number)
|
|
183
|
+
number.to_s.reverse.scan(/.{1,3}/).join(",").reverse
|
|
184
|
+
end
|
|
84
185
|
|
|
85
|
-
|
|
186
|
+
def render_line(line)
|
|
187
|
+
@io.print "\r\e[2K#{line}"
|
|
188
|
+
@io.flush
|
|
189
|
+
@line_open = true
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def clear_line
|
|
193
|
+
return unless @pretty && @line_open
|
|
194
|
+
|
|
195
|
+
@io.print "\r\e[2K"
|
|
86
196
|
@io.flush
|
|
87
|
-
@line_width = 0
|
|
88
197
|
@line_open = false
|
|
89
198
|
end
|
|
199
|
+
|
|
200
|
+
def color(style, text)
|
|
201
|
+
return text unless @color
|
|
202
|
+
|
|
203
|
+
"#{ANSI.fetch(style)}#{text}#{ANSI[:reset]}"
|
|
204
|
+
end
|
|
90
205
|
end
|
|
91
206
|
private_constant :ProgressReporter
|
|
92
207
|
MIN_INTEGER = -(2**63)
|
|
@@ -767,7 +882,8 @@ module Neo4jBolt
|
|
|
767
882
|
batch_size = [batch_size, reduced_batch_size].min
|
|
768
883
|
progress&.note(
|
|
769
884
|
"Neo4j rejected a #{kind} batch of #{slice.size} for transaction memory; " \
|
|
770
|
-
"retrying with batches of #{batch_size}."
|
|
885
|
+
"retrying with batches of #{batch_size}.",
|
|
886
|
+
level: :warning
|
|
771
887
|
)
|
|
772
888
|
end
|
|
773
889
|
end
|
|
@@ -822,7 +938,8 @@ module Neo4jBolt
|
|
|
822
938
|
|
|
823
939
|
batch_size = [batch_size / 2, 1].max
|
|
824
940
|
progress&.note(
|
|
825
|
-
"Neo4j rejected a cleanup batch for transaction memory; retrying with batches of #{batch_size}."
|
|
941
|
+
"Neo4j rejected a cleanup batch for transaction memory; retrying with batches of #{batch_size}.",
|
|
942
|
+
level: :warning
|
|
826
943
|
)
|
|
827
944
|
end
|
|
828
945
|
end
|