sqlconv 1.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|