sqlconv 1.0 → 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 +113 -90
- 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
|
|
8
45
|
end
|
|
9
46
|
|
|
10
|
-
|
|
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?
|
|
53
|
+
end
|
|
54
|
+
|
|
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}/)
|
|
@@ -46,16 +90,15 @@ Selector = Struct.new(:want, :func, :text, :zero, :thru, :reps, :from, :till)
|
|
|
46
90
|
# convert user request into selectors
|
|
47
91
|
def grok(want)
|
|
48
92
|
(want || "1-").strip.split(/\s*,\s*/).map do |item|
|
|
49
|
-
item =~ %r!^
|
|
50
|
-
(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
)$
|
|
93
|
+
item =~ %r!^(?:(\d+)\*)?(?:(?:( # $1: repeat
|
|
94
|
+
(?:\d+(?=\())|[a-zA-Z]\w*) # $2: function name
|
|
95
|
+
(\()?)?(?: # $3: optional paren
|
|
96
|
+
(?:(['"])(.*?)\4)? | # $4: quote, $5: literal
|
|
97
|
+
(0) | # $6: zero
|
|
98
|
+
((?>[1-9]\d*))? # $7: from
|
|
99
|
+
((?<=\d)-|-(?=\d))? # $8: thru
|
|
100
|
+
((?>[1-9]\d*))? # $9: till
|
|
101
|
+
)\)?)$
|
|
59
102
|
!iox or raise "invalid selector item '#{item}'"
|
|
60
103
|
Selector.new(*$~.values_at(0, 2, 5, 6, 8), *$~.values_at(1, 7, 9).map {|e| e&.to_i })
|
|
61
104
|
end or raise "invalid selector '#{want}'"
|
|
@@ -65,20 +108,37 @@ end
|
|
|
65
108
|
def table(cols, rows)
|
|
66
109
|
cols.is_a?(Array) && cols.size > 0 or return
|
|
67
110
|
rows.is_a?(Array) && rows.size > 0 or return
|
|
68
|
-
join = "
|
|
111
|
+
join = " │ "
|
|
69
112
|
both = [cols] + rows
|
|
70
113
|
flip = both.transpose
|
|
71
114
|
wide = flip.map {|row| row.map {|col| col.to_s.size }.max }
|
|
72
115
|
pict = wide.map {|len| "%-#{len}.#{len}s" }.join(join)
|
|
73
116
|
pict = [join, pict, join].join.strip
|
|
74
|
-
|
|
117
|
+
base = (pict % ([""] * cols.size))[1...-1]
|
|
118
|
+
ltop = "┌" + base.tr("│ ", "┬─") + "┐"
|
|
119
|
+
lmid = "├" + base.tr("│ ", "┼─") + "┤"
|
|
120
|
+
lbot = "└" + base.tr("│ ", "┴─") + "┘"
|
|
75
121
|
seen = -1
|
|
76
|
-
puts "",
|
|
122
|
+
puts "", ltop
|
|
77
123
|
both.each do |vals|
|
|
78
124
|
puts pict % vals
|
|
79
|
-
puts
|
|
125
|
+
puts lmid if (seen += 1) == 0
|
|
80
126
|
end
|
|
81
|
-
puts
|
|
127
|
+
puts lbot, "#{seen} rows displayed", ""
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def escape(str)
|
|
131
|
+
str =~ /\A(\d+|null)\z/i ? $1 : %|'#{str.gsub("'", "\\\\'")}'|
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def unescape(str, nulls=false)
|
|
135
|
+
str =~ /\A['"]/ and return str[1..-2].gsub("|","~").gsub("''", "'")
|
|
136
|
+
str == "NULL" and return "" unless nulls
|
|
137
|
+
str
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def unescape!(str)
|
|
141
|
+
unescape(str, true)
|
|
82
142
|
end
|
|
83
143
|
|
|
84
144
|
# convert the insert statements
|
|
@@ -102,13 +162,20 @@ def conv(tab1, map1, tab2, map2, mode, dump)
|
|
|
102
162
|
# find source table
|
|
103
163
|
data.string = dump.read # dump.read(5000) # TODO: Add streaming support
|
|
104
164
|
into = data.scan_for(/insert into (['"`]?)#{tab1}\1 values /io)
|
|
105
|
-
into or
|
|
165
|
+
into or abort "unable to find insert statements for the '#{tab1}' table"
|
|
106
166
|
|
|
107
167
|
# if needed, output pipes header
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
168
|
+
case mode
|
|
169
|
+
when "psv", "tsv"
|
|
170
|
+
puts map2.gsub(",", mode == "psv" ? "|" : "\t") if map2
|
|
171
|
+
lean = true
|
|
172
|
+
when "csv"
|
|
173
|
+
$csv = Censive.writer(out: $stdout)
|
|
174
|
+
$csv << map2.split(",") if map2
|
|
175
|
+
lean = true
|
|
176
|
+
when "table"
|
|
111
177
|
rows = []
|
|
178
|
+
lean = true
|
|
112
179
|
end
|
|
113
180
|
|
|
114
181
|
# process each line
|
|
@@ -117,7 +184,8 @@ def conv(tab1, map1, tab2, map2, mode, dump)
|
|
|
117
184
|
# parse insert statements
|
|
118
185
|
if data.scan_str("(") or data.scan_str(into + "(")
|
|
119
186
|
cols = data.scan_while(/('.*?(?<!\\)'|(?>[^',()]+)|,)/, 2)
|
|
120
|
-
cols.empty? and
|
|
187
|
+
cols.empty? and abort "bad sql parse: '#{line}'"
|
|
188
|
+
cols.map! {|item| unescape(item)} if lean
|
|
121
189
|
data.scan(/\)[;,]\s*/)
|
|
122
190
|
else
|
|
123
191
|
break
|
|
@@ -127,19 +195,14 @@ def conv(tab1, map1, tab2, map2, mode, dump)
|
|
|
127
195
|
unless len1
|
|
128
196
|
len1 = cols.size
|
|
129
197
|
if mode == "show"
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
puts sep, "| col | %-*.*s |" % [max, max, 'data'], sep
|
|
133
|
-
len1.times do |pos|
|
|
134
|
-
val = cols[pos]
|
|
135
|
-
val[max-3..-1] = '...' if val.size > max
|
|
136
|
-
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]
|
|
137
200
|
end
|
|
138
|
-
|
|
201
|
+
table %w[ col data], data
|
|
139
202
|
exit
|
|
140
203
|
end
|
|
141
204
|
need.each do |item|
|
|
142
|
-
item.text &&=
|
|
205
|
+
item.text &&= escape(item.text) unless lean
|
|
143
206
|
if (len2 = [item.from, item.till, 0].compact.max) > len1
|
|
144
207
|
warn "selector '#{item.want}' referenced source column #{len2}, but only #{len1} are defined"
|
|
145
208
|
cols &&= nil
|
|
@@ -159,9 +222,14 @@ def conv(tab1, map1, tab2, map2, mode, dump)
|
|
|
159
222
|
case item.func
|
|
160
223
|
when "rand" then ours.push("'random number here!'")
|
|
161
224
|
when "n","null" then ours.push("null")
|
|
162
|
-
when "z" then ours.push((val = cols[item.from-1]) == "NULL" ? 0
|
|
225
|
+
when "z" then ours.push((val = cols[item.from -1]) == "NULL" ? 0 : val)
|
|
226
|
+
when /^(\d+)$/
|
|
227
|
+
val = cols[item.func.to_i - 1]
|
|
228
|
+
val = unescape(val) unless lean
|
|
229
|
+
val = val[0, item.from]
|
|
230
|
+
ours.push(val)
|
|
163
231
|
else
|
|
164
|
-
defined?(item.func) == "method" or
|
|
232
|
+
defined?(item.func) == "method" or abort "undefined function '#{item.func}'"
|
|
165
233
|
ours.push *(send item.func, *Array[cols[item.from-1]])
|
|
166
234
|
end
|
|
167
235
|
when item.text # literal
|
|
@@ -186,7 +254,7 @@ def conv(tab1, map1, tab2, map2, mode, dump)
|
|
|
186
254
|
# perform one-time check on destination column counts
|
|
187
255
|
unless len2
|
|
188
256
|
if map2 and (len2 = map2.split(",").size) != ours.size
|
|
189
|
-
warn "destination column mismatch (#{
|
|
257
|
+
warn "destination column mismatch (#{ours.size} sourced but #{len2} targeted)"
|
|
190
258
|
cols &&= nil
|
|
191
259
|
else
|
|
192
260
|
len2 = ours.size
|
|
@@ -195,67 +263,22 @@ def conv(tab1, map1, tab2, map2, mode, dump)
|
|
|
195
263
|
end
|
|
196
264
|
|
|
197
265
|
# generate output
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
rows << ours.dup
|
|
205
|
-
else
|
|
206
|
-
puts [pref, ours * ",", ");"].join
|
|
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
|
|
207
272
|
end
|
|
208
273
|
end
|
|
209
274
|
|
|
210
275
|
# output table
|
|
211
276
|
if mode == "table"
|
|
212
|
-
cols = map2 ? map2.split(
|
|
277
|
+
cols = map2 ? map2.split(",") : rows[0].size.times.map {|i| "col#{i+1}"}
|
|
213
278
|
table cols, rows
|
|
214
279
|
end
|
|
215
280
|
end
|
|
216
281
|
|
|
217
|
-
# ==[
|
|
218
|
-
|
|
219
|
-
argv = -1
|
|
220
|
-
ARGV.size.times do
|
|
221
|
-
if ARGV[argv += 1] == "-x"
|
|
222
|
-
begin
|
|
223
|
-
require plugin = ARGV[argv += 1]
|
|
224
|
-
rescue LoadError
|
|
225
|
-
die "unable to load the '#{plugin}' plugin"
|
|
226
|
-
end
|
|
227
|
-
ARGV.slice!((argv -= 2) + 1, 2)
|
|
228
|
-
end
|
|
229
|
-
end
|
|
230
|
-
|
|
231
|
-
# mutually exclusive options
|
|
232
|
-
if ARGV.delete "-s" then mode = "show"
|
|
233
|
-
elsif ARGV.delete "-r" then mode = "replace"
|
|
234
|
-
elsif ARGV.delete "-p" then mode = "pipes"
|
|
235
|
-
elsif ARGV.delete "-t" then mode = "table"; end
|
|
236
|
-
|
|
237
|
-
if ARGV.shift =~ /^([a-z][-\w]*):?(.+)?$/
|
|
238
|
-
tab1 = $1
|
|
239
|
-
map1 = $2
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
if ARGV.size > 0 and !File.exists?(ARGV.first)
|
|
243
|
-
if ARGV.shift =~ /^((?>[a-z]?[-\w]*)(?::|$))?(.+)?$/
|
|
244
|
-
$1.to_s.size > 0 and tab2 = $1.chomp(":")
|
|
245
|
-
$2.to_s.size > 0 and map2 = $2.squeeze(',').sub(/^,+/,'').sub(/,+$/,'')
|
|
246
|
-
end
|
|
247
|
-
tab1 = nil if $0.size == 0 # no match, show usage
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
tab1 or die [
|
|
251
|
-
"Usage: #{File.basename $0} <options> " +
|
|
252
|
-
"<src_table>(:[sel1,sel2,...]) " +
|
|
253
|
-
"[dst_table][:][col1,col2,...] file",
|
|
254
|
-
" -p (output pipe separated values instead of SQL",
|
|
255
|
-
" -r (use 'replace into' instead of 'insert into')",
|
|
256
|
-
" -s (show column indexes and values for the first row)",
|
|
257
|
-
" -t (display output as a formatted table)",
|
|
258
|
-
" -x <plugin1.rb> [-x <plugin2.rb>]...]",
|
|
259
|
-
] * "\n"
|
|
282
|
+
# ==[ Let 'er rip! ]==
|
|
260
283
|
|
|
261
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
|