sqlconv 1.1 → 1.2.0
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/bin/sqlconv +81 -76
- data/sqlconv.gemspec +1 -1
- metadata +6 -8
- data/.ruby-version +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e823f40e2647848b38ea093b7127f31ea1984ca5f178826d595e72fa7f08c71b
|
4
|
+
data.tar.gz: 6467e992cf18952ec3dc357ef60f93d6ba95d488ea9a1c8edb6d3c6efe576f29
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e725ce923d38cdc32c5a824f07e0734ebfaa06a371c5a4ca0b1ccf671880f89d5e68b413f1f40d4900ce95fcce61a78977ac768779de0c677c1c0f90461d654b
|
7
|
+
data.tar.gz: ee0fb55e02a53abee3f1a8cebfc2be79e5001f460ff7f45f8989cbfb3fcb5e412f03f5a55833823d9cd184afb8b7d7d5c967db4f4269b9ec99e1d54e314633a1
|
data/bin/sqlconv
CHANGED
@@ -1,15 +1,59 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
VERSION = "1.2.0"
|
4
|
+
|
3
5
|
STDOUT.sync = true
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
|
7
|
+
require "censive"
|
8
|
+
require "optparse"
|
9
|
+
require "strscan"
|
10
|
+
|
11
|
+
trap("INT" ) { abort "\n" }
|
12
|
+
|
13
|
+
def die!; abort "#{File.basename($0)}: invalid usage, use -h for help"; end
|
14
|
+
|
15
|
+
OptionParser.new.instance_eval do
|
16
|
+
@banner = "usage: #{program_name} <options> <src_table>(:[sel1,sel2,...]) [dst_table][:][col1,col2,...] <dumpfile.sql or ARGF>"
|
17
|
+
|
18
|
+
on "--csv" , "Output comma separated values instead of SQL"
|
19
|
+
on "--psv" , "Output pipe separated values instead of SQL"
|
20
|
+
on "--tsv" , "Output tab separated values instead of SQL"
|
21
|
+
on "-p", "--plugin <plugin1.rb,...>", "Comma separated list of plugins"
|
22
|
+
on "-r", "--replace" , "Use 'replace into' instead of 'insert into'"
|
23
|
+
on "-s", "--show" , "Show column indexes and values for the first row"
|
24
|
+
on "-t", "--table" , "Display output as a formatted table"
|
25
|
+
|
26
|
+
on "-h", "--help" , "Show help and command usage" do Kernel.abort to_s; end
|
27
|
+
on "-v", "--version" , "Show version number" do Kernel.abort "#{program_name} #{VERSION}"; end
|
28
|
+
|
29
|
+
self
|
30
|
+
end.parse!(into: opts={}) rescue abort($!.message)
|
31
|
+
|
32
|
+
csvs = opts[:csv ] and mode = "csv"
|
33
|
+
pipe = opts[:psv ] and mode = "psv"
|
34
|
+
tabs = opts[:tsv ] and mode = "tsv"
|
35
|
+
nice = opts[:table ] and mode = "table"
|
36
|
+
repl = opts[:replace] and mode = "replace"
|
37
|
+
show = opts[:show ] and mode = "show"
|
38
|
+
plug = opts[:plugin].to_s.downcase.split(",")
|
39
|
+
|
40
|
+
die! if [csvs, pipe, tabs, nice, repl, show].compact.size > 1
|
41
|
+
|
42
|
+
if ARGV.shift =~ /^([a-z][-\w]*):?(.+)?$/i
|
43
|
+
tab1 = $1
|
44
|
+
map1 = $2
|
45
|
+
end
|
46
|
+
|
47
|
+
if !ARGV.empty? and !File.exist?(ARGV.first)
|
48
|
+
if ARGV.shift =~ /^((?>[a-z]?[-\w]*)(?::|$))?(.+)?$/i
|
49
|
+
$1.to_s.size > 0 and tab2 = $1.chomp(":")
|
50
|
+
$2.to_s.size > 0 and map2 = $2.squeeze(",").sub(/^,+/,"").sub(/,+$/,"")
|
51
|
+
end
|
52
|
+
die! if $0.empty?
|
8
53
|
end
|
9
54
|
|
10
|
-
|
55
|
+
# ==[ Helpers ]==
|
11
56
|
|
12
|
-
# parsing helpers
|
13
57
|
class StringScanner
|
14
58
|
def scan_for(regx)
|
15
59
|
data = scan_until(Regexp === regx ? regx : /#{regx}/)
|
@@ -64,20 +108,23 @@ end
|
|
64
108
|
def table(cols, rows)
|
65
109
|
cols.is_a?(Array) && cols.size > 0 or return
|
66
110
|
rows.is_a?(Array) && rows.size > 0 or return
|
67
|
-
join = "
|
111
|
+
join = " │ "
|
68
112
|
both = [cols] + rows
|
69
113
|
flip = both.transpose
|
70
114
|
wide = flip.map {|row| row.map {|col| col.to_s.size }.max }
|
71
115
|
pict = wide.map {|len| "%-#{len}.#{len}s" }.join(join)
|
72
116
|
pict = [join, pict, join].join.strip
|
73
|
-
|
117
|
+
base = (pict % ([""] * cols.size))[1...-1]
|
118
|
+
ltop = "┌" + base.tr("│ ", "┬─") + "┐"
|
119
|
+
lmid = "├" + base.tr("│ ", "┼─") + "┤"
|
120
|
+
lbot = "└" + base.tr("│ ", "┴─") + "┘"
|
74
121
|
seen = -1
|
75
|
-
puts "",
|
122
|
+
puts "", ltop
|
76
123
|
both.each do |vals|
|
77
124
|
puts pict % vals
|
78
|
-
puts
|
125
|
+
puts lmid if (seen += 1) == 0
|
79
126
|
end
|
80
|
-
puts
|
127
|
+
puts lbot, "#{seen} rows displayed", ""
|
81
128
|
end
|
82
129
|
|
83
130
|
def escape(str)
|
@@ -86,7 +133,7 @@ end
|
|
86
133
|
|
87
134
|
def unescape(str, nulls=false)
|
88
135
|
str =~ /\A['"]/ and return str[1..-2].gsub("|","~").gsub("''", "'")
|
89
|
-
str ==
|
136
|
+
str == "NULL" and return "" unless nulls
|
90
137
|
str
|
91
138
|
end
|
92
139
|
|
@@ -115,13 +162,18 @@ def conv(tab1, map1, tab2, map2, mode, dump)
|
|
115
162
|
# find source table
|
116
163
|
data.string = dump.read # dump.read(5000) # TODO: Add streaming support
|
117
164
|
into = data.scan_for(/insert into (['"`]?)#{tab1}\1 values /io)
|
118
|
-
into or
|
165
|
+
into or abort "unable to find insert statements for the '#{tab1}' table"
|
119
166
|
|
120
167
|
# if needed, output pipes header
|
121
|
-
|
122
|
-
|
168
|
+
case mode
|
169
|
+
when "psv", "tsv"
|
170
|
+
puts map2.gsub(",", mode == "psv" ? "|" : "\t") if map2
|
123
171
|
lean = true
|
124
|
-
|
172
|
+
when "csv"
|
173
|
+
$csv = Censive.writer(out: $stdout)
|
174
|
+
$csv << map2.split(",") if map2
|
175
|
+
lean = true
|
176
|
+
when "table"
|
125
177
|
rows = []
|
126
178
|
lean = true
|
127
179
|
end
|
@@ -132,7 +184,7 @@ def conv(tab1, map1, tab2, map2, mode, dump)
|
|
132
184
|
# parse insert statements
|
133
185
|
if data.scan_str("(") or data.scan_str(into + "(")
|
134
186
|
cols = data.scan_while(/('.*?(?<!\\)'|(?>[^',()]+)|,)/, 2)
|
135
|
-
cols.empty? and
|
187
|
+
cols.empty? and abort "bad sql parse: '#{line}'"
|
136
188
|
cols.map! {|item| unescape(item)} if lean
|
137
189
|
data.scan(/\)[;,]\s*/)
|
138
190
|
else
|
@@ -143,15 +195,10 @@ def conv(tab1, map1, tab2, map2, mode, dump)
|
|
143
195
|
unless len1
|
144
196
|
len1 = cols.size
|
145
197
|
if mode == "show"
|
146
|
-
|
147
|
-
|
148
|
-
puts sep, "| col | %-*.*s |" % [max, max, 'data'], sep
|
149
|
-
len1.times do |pos|
|
150
|
-
val = cols[pos]
|
151
|
-
val[max-3..-1] = '...' if val.size > max
|
152
|
-
puts "| %3d | %-*.*s | " % [pos + 1, max, max, val]
|
198
|
+
data = cols.map.with_index do |data, i|
|
199
|
+
[i + 1, data.size > 32 ? data[...-3] + "..." : data]
|
153
200
|
end
|
154
|
-
|
201
|
+
table %w[ col data], data
|
155
202
|
exit
|
156
203
|
end
|
157
204
|
need.each do |item|
|
@@ -182,7 +229,7 @@ def conv(tab1, map1, tab2, map2, mode, dump)
|
|
182
229
|
val = val[0, item.from]
|
183
230
|
ours.push(val)
|
184
231
|
else
|
185
|
-
defined?(item.func) == "method" or
|
232
|
+
defined?(item.func) == "method" or abort "undefined function '#{item.func}'"
|
186
233
|
ours.push *(send item.func, *Array[cols[item.from-1]])
|
187
234
|
end
|
188
235
|
when item.text # literal
|
@@ -207,7 +254,7 @@ def conv(tab1, map1, tab2, map2, mode, dump)
|
|
207
254
|
# perform one-time check on destination column counts
|
208
255
|
unless len2
|
209
256
|
if map2 and (len2 = map2.split(",").size) != ours.size
|
210
|
-
warn "destination column mismatch (#{
|
257
|
+
warn "destination column mismatch (#{ours.size} sourced but #{len2} targeted)"
|
211
258
|
cols &&= nil
|
212
259
|
else
|
213
260
|
len2 = ours.size
|
@@ -216,64 +263,22 @@ def conv(tab1, map1, tab2, map2, mode, dump)
|
|
216
263
|
end
|
217
264
|
|
218
265
|
# generate output
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
266
|
+
case mode
|
267
|
+
when "psv" then puts ours * "|"
|
268
|
+
when "tsv" then puts ours * "\t"
|
269
|
+
when "csv" then $csv << ours
|
270
|
+
when "table" then rows << ours.dup
|
271
|
+
else puts [pref, ours * ",", ");"].join
|
225
272
|
end
|
226
273
|
end
|
227
274
|
|
228
275
|
# output table
|
229
276
|
if mode == "table"
|
230
|
-
cols = map2 ? map2.split(
|
277
|
+
cols = map2 ? map2.split(",") : rows[0].size.times.map {|i| "col#{i+1}"}
|
231
278
|
table cols, rows
|
232
279
|
end
|
233
280
|
end
|
234
281
|
|
235
|
-
# ==[
|
236
|
-
|
237
|
-
argv = -1
|
238
|
-
ARGV.size.times do
|
239
|
-
if ARGV[argv += 1] == "-x"
|
240
|
-
begin
|
241
|
-
require plugin = ARGV[argv += 1]
|
242
|
-
rescue LoadError
|
243
|
-
die "unable to load the '#{plugin}' plugin"
|
244
|
-
end
|
245
|
-
ARGV.slice!((argv -= 2) + 1, 2)
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
|
-
# mutually exclusive options
|
250
|
-
if ARGV.delete "-s" then mode = "show"
|
251
|
-
elsif ARGV.delete "-r" then mode = "replace"
|
252
|
-
elsif ARGV.delete "-p" then mode = "pipes"
|
253
|
-
elsif ARGV.delete "-t" then mode = "table"; end
|
254
|
-
|
255
|
-
if ARGV.shift =~ /^([a-z][-\w]*):?(.+)?$/
|
256
|
-
tab1 = $1
|
257
|
-
map1 = $2
|
258
|
-
end
|
259
|
-
|
260
|
-
if ARGV.size > 0 and !File.exists?(ARGV.first)
|
261
|
-
if ARGV.shift =~ /^((?>[a-z]?[-\w]*)(?::|$))?(.+)?$/
|
262
|
-
$1.to_s.size > 0 and tab2 = $1.chomp(":")
|
263
|
-
$2.to_s.size > 0 and map2 = $2.squeeze(',').sub(/^,+/,'').sub(/,+$/,'')
|
264
|
-
end
|
265
|
-
tab1 = nil if $0.size == 0 # no match, show usage
|
266
|
-
end
|
267
|
-
|
268
|
-
tab1 or die [
|
269
|
-
"Usage: #{File.basename $0} <options> " +
|
270
|
-
"<src_table>(:[sel1,sel2,...]) " +
|
271
|
-
"[dst_table][:][col1,col2,...] file",
|
272
|
-
" -p (output pipe separated values instead of SQL",
|
273
|
-
" -r (use 'replace into' instead of 'insert into')",
|
274
|
-
" -s (show column indexes and values for the first row)",
|
275
|
-
" -t (display output as a formatted table)",
|
276
|
-
" -x <plugin1.rb> [-x <plugin2.rb>]...]",
|
277
|
-
] * "\n"
|
282
|
+
# ==[ Let 'er rip! ]==
|
278
283
|
|
279
284
|
conv tab1, map1, tab2 || tab1, map2, mode, ARGF
|
data/sqlconv.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sqlconv
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Steve Shreeve
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-03-13 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Allows mapping columns from a source to a destination table
|
14
14
|
email: steve.shreeve@gmail.com
|
@@ -17,7 +17,6 @@ executables:
|
|
17
17
|
extensions: []
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
|
-
- ".ruby-version"
|
21
20
|
- Gemfile
|
22
21
|
- LICENSE
|
23
22
|
- README.md
|
@@ -27,7 +26,7 @@ homepage: https://github.com/shreeve/sqlconv
|
|
27
26
|
licenses:
|
28
27
|
- MIT
|
29
28
|
metadata: {}
|
30
|
-
post_install_message:
|
29
|
+
post_install_message:
|
31
30
|
rdoc_options: []
|
32
31
|
require_paths:
|
33
32
|
- lib
|
@@ -42,9 +41,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
42
41
|
- !ruby/object:Gem::Version
|
43
42
|
version: '0'
|
44
43
|
requirements: []
|
45
|
-
|
46
|
-
|
47
|
-
signing_key:
|
44
|
+
rubygems_version: 3.4.8
|
45
|
+
signing_key:
|
48
46
|
specification_version: 4
|
49
47
|
summary: Handy utility to massage MySQL dump files
|
50
48
|
test_files: []
|
data/.ruby-version
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
2.5
|