sqlconv 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.ruby-version +1 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +14 -0
- data/bin/sqlconv +192 -0
- data/sqlconv.gemspec +14 -0
- metadata +50 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ad9e122f339ca74831a6c206d8b148fc89e182ca51d33efbd2732d5a14ecd8c5
|
4
|
+
data.tar.gz: 54473e10cdd3db7d10db62bdc36d851f76636cf09a6e0ecc9928dca1cf607f97
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 77c740a056cba408b3b85d56abfd80444b2c61acc109fc1a982fae6a4cbd5de55ff99043eaff3e747aca5b2d03a791f095120cd49d854b50be9dd04fd31c2423
|
7
|
+
data.tar.gz: 2abfb13941ae049ae887509be5c2ddf194d90f085249d01a54830611659decd3a281092d27635eaa849dcccd5b0fbd31adf045848b0f238017a89a17d58e1fda
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.5
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Steve Shreeve
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# sqlconv
|
2
|
+
|
3
|
+
`sqlconv` is a handy utility, written in Ruby, to massage MySQL dump files.
|
4
|
+
It allows columns from a source table to be mapped to a destination table.
|
5
|
+
|
6
|
+
## Examples
|
7
|
+
|
8
|
+
```shell
|
9
|
+
sqlconv.rb 'corporate_users:1,,3-4,8-6,9-,2*now()' users < original-export.sql
|
10
|
+
```
|
11
|
+
|
12
|
+
## License
|
13
|
+
|
14
|
+
This software is licensed under terms of the MIT License.
|
data/bin/sqlconv
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
STDOUT.sync = true
|
4
|
+
|
5
|
+
require 'strscan'
|
6
|
+
|
7
|
+
# parsing helpers
|
8
|
+
class StringScanner
|
9
|
+
def scan_for(regx)
|
10
|
+
data = scan_until(Regexp === regx ? regx : /#{regx}/)
|
11
|
+
size = matched_size or return
|
12
|
+
data[-size..-1]
|
13
|
+
end
|
14
|
+
|
15
|
+
def scan_str(str)
|
16
|
+
if string[pos, str.size] == str
|
17
|
+
self.pos += str.size
|
18
|
+
str
|
19
|
+
else
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def scan_while(regx, skip=nil)
|
25
|
+
seen = -1
|
26
|
+
list = []
|
27
|
+
while item = scan(regx)
|
28
|
+
if skip
|
29
|
+
list << item if (seen += 1) % skip == 0
|
30
|
+
else
|
31
|
+
list << item
|
32
|
+
end
|
33
|
+
end
|
34
|
+
list
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# struct for dealing with selectors
|
39
|
+
Selector = Struct.new(:want, :func, :text, :zero, :thru, :reps, :from, :till)
|
40
|
+
|
41
|
+
# convert user request into selectors
|
42
|
+
def grok(want)
|
43
|
+
(want || "1-").strip.split(/\s*,\s*/).map do |item|
|
44
|
+
item =~ %r!^
|
45
|
+
(?:(\d+)\*)?(?: # $1: repeat
|
46
|
+
(?:([a-zA-Z]\w*)(\()?)?(?: # $2: function, $3: optional paren
|
47
|
+
(?:(['"])(.*?)\4)? | # $4: quote, $5: literal
|
48
|
+
(0) | # $6: zero
|
49
|
+
((?>[1-9]\d*))? # $7: from
|
50
|
+
((?<=\d)-|-(?=\d))? # $8: thru
|
51
|
+
((?>[1-9]\d*))? # $9: till
|
52
|
+
)\)?
|
53
|
+
)$
|
54
|
+
!iox or raise "invalid selector item '#{item}'"
|
55
|
+
Selector.new(*$~.values_at(0, 2, 5, 6, 8), *$~.values_at(1, 7, 9).map {|e| e&.to_i })
|
56
|
+
end or raise "invalid selector '#{want}'"
|
57
|
+
end
|
58
|
+
|
59
|
+
# convert the insert statements
|
60
|
+
def conv(tab1, map1, tab2, map2, dump)
|
61
|
+
data = StringScanner.new("")
|
62
|
+
need = grok(map1)
|
63
|
+
cols = nil
|
64
|
+
len1 = nil
|
65
|
+
len2 = nil
|
66
|
+
ours = []
|
67
|
+
posn = 0
|
68
|
+
|
69
|
+
# statement prefix
|
70
|
+
pref = [
|
71
|
+
"insert into #{tab2 || tab1}",
|
72
|
+
(" (#{map2})" if map2),
|
73
|
+
" values (",
|
74
|
+
].compact.join
|
75
|
+
|
76
|
+
# find source table
|
77
|
+
data.string = dump.read # dump.read(5000) # TODO: Add streaming support
|
78
|
+
into = data.scan_for(/insert into (['"`]?)#{tab1}\1 values /io)
|
79
|
+
|
80
|
+
# process each line
|
81
|
+
loop do
|
82
|
+
|
83
|
+
# parse insert statements
|
84
|
+
if data.scan_str("(") or data.scan_str(into + "(")
|
85
|
+
/(?:insert into (['"`]?)#{tab1}\1 values )?\(/io
|
86
|
+
cols = data.scan_while(/('.*?(?<!\\)'|(?>[^',()]+)|,)/, 2)
|
87
|
+
cols.empty? and (warn "bad sql parse: '#{line}'" or next)
|
88
|
+
data.scan(/\)[;,]\s*/)
|
89
|
+
else
|
90
|
+
break
|
91
|
+
end
|
92
|
+
|
93
|
+
# perform one-time check on source column bounds
|
94
|
+
unless len1
|
95
|
+
len1 = cols.size
|
96
|
+
need.each do |item|
|
97
|
+
item.text &&= ["'", item.text.gsub("'", "\\\\'"), "'"].join
|
98
|
+
if (len2 = [item.from, item.till, 0].compact.max) > len1
|
99
|
+
warn "selector '#{item.want}' referenced source column #{len2}, but only #{len1} are defined"
|
100
|
+
cols &&= nil
|
101
|
+
end
|
102
|
+
end
|
103
|
+
cols or exit
|
104
|
+
len1 = cols.size
|
105
|
+
len2 = nil # we hijacked len2, so clear it
|
106
|
+
end
|
107
|
+
|
108
|
+
# pluck desired columns
|
109
|
+
ours.clear
|
110
|
+
need.each do |item|
|
111
|
+
(item.reps || 1).times do # repeats
|
112
|
+
case
|
113
|
+
when item.func # function (TODO: honor text/zero/from/till)
|
114
|
+
case item.func
|
115
|
+
when "rand" then ours.push("'random number here!'")
|
116
|
+
when "n","null" then ours.push("null")
|
117
|
+
else abort "undefined function '#{item.func}'"
|
118
|
+
end
|
119
|
+
when item.text# literal
|
120
|
+
ours.push(item.text)
|
121
|
+
when item.zero # zero
|
122
|
+
ours.push(0)
|
123
|
+
when item.thru # range
|
124
|
+
from = item.from || 1
|
125
|
+
till = item.till || len1
|
126
|
+
ours.concat case till <=> from
|
127
|
+
when 0,1 then cols[(from-1)..(till-1)]
|
128
|
+
when -1 then cols[(till-1)..(from-1)].reverse
|
129
|
+
end
|
130
|
+
when item.from || item.till # one column
|
131
|
+
ours.push(cols[(item.from || item.till) - 1])
|
132
|
+
else # null
|
133
|
+
ours.push("null")
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# perform one-time check on destination column counts
|
139
|
+
unless len2
|
140
|
+
if map2 and (len2 = map2.split(",").size) != ours.size
|
141
|
+
warn "destination column mismatch (#{len2} defined but #{ours.size} generated)"
|
142
|
+
cols &&= nil
|
143
|
+
else
|
144
|
+
len2 = ours.size
|
145
|
+
end
|
146
|
+
cols or exit
|
147
|
+
end
|
148
|
+
|
149
|
+
# output insert statement
|
150
|
+
puts [pref, ours * ",", ");"].join
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# ==[ invoke the cli ]==
|
155
|
+
|
156
|
+
if ARGV.shift =~ /^([a-z][-\w]*):?(.+)?$/
|
157
|
+
tab1 = $1
|
158
|
+
map1 = $2
|
159
|
+
end
|
160
|
+
|
161
|
+
if ARGV.size > 0 and !File.exists?(ARGV.first)
|
162
|
+
if ARGV.shift =~ /^((?>[a-z]?[-\w]*)(?::|$))?(.+)?$/
|
163
|
+
$1.to_s.size > 0 and tab2 = $1.chomp(":")
|
164
|
+
$2.to_s.size > 0 and map2 = $2.squeeze(',').sub(/^,+/,'').sub(/,+$/,'')
|
165
|
+
end
|
166
|
+
tab1 = nil if $0.size == 0 # no match, show usage
|
167
|
+
end
|
168
|
+
|
169
|
+
tab1 or (warn [
|
170
|
+
"Usage: #{File.basename $0}",
|
171
|
+
"<src_table>(:[sel1,sel2,...])",
|
172
|
+
"[dst_table][:][col1,col2,...] file"
|
173
|
+
] * ' ' or exit)
|
174
|
+
|
175
|
+
conv tab1, map1, tab2 || tab1, map2, ARGF
|
176
|
+
|
177
|
+
__END__
|
178
|
+
|
179
|
+
# show opt parsing
|
180
|
+
p [:tab1, tab1]
|
181
|
+
p [:map1, map1]
|
182
|
+
p [:tab2, tab2]
|
183
|
+
p [:map2, map2]
|
184
|
+
p [:argv, ARGV]
|
185
|
+
exit
|
186
|
+
|
187
|
+
# values for testing
|
188
|
+
tab1 = 'adonis_schema'
|
189
|
+
map1 = nil
|
190
|
+
tab2 = nil
|
191
|
+
map2 = nil
|
192
|
+
ARGV.push 'db1.sql'
|
data/sqlconv.gemspec
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "sqlconv"
|
5
|
+
s.version = "0.5.0"
|
6
|
+
s.author = "Steve Shreeve"
|
7
|
+
s.email = "steve.shreeve@gmail.com"
|
8
|
+
s.summary = "Handy utility to massage MySQL dump files"
|
9
|
+
s.description = "Allows mapping columns from a source to a destination table"
|
10
|
+
s.homepage = "https://github.com/shreeve/sqlconv"
|
11
|
+
s.license = "MIT"
|
12
|
+
s.files = `git ls-files`.split("\n") - %w[.gitignore]
|
13
|
+
s.executables = `cd bin && git ls-files .`.split("\n")
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sqlconv
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Steve Shreeve
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-06-01 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Allows mapping columns from a source to a destination table
|
14
|
+
email: steve.shreeve@gmail.com
|
15
|
+
executables:
|
16
|
+
- sqlconv
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- ".ruby-version"
|
21
|
+
- Gemfile
|
22
|
+
- LICENSE
|
23
|
+
- README.md
|
24
|
+
- bin/sqlconv
|
25
|
+
- sqlconv.gemspec
|
26
|
+
homepage: https://github.com/shreeve/sqlconv
|
27
|
+
licenses:
|
28
|
+
- MIT
|
29
|
+
metadata: {}
|
30
|
+
post_install_message:
|
31
|
+
rdoc_options: []
|
32
|
+
require_paths:
|
33
|
+
- lib
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
requirements: []
|
45
|
+
rubyforge_project:
|
46
|
+
rubygems_version: 2.7.6
|
47
|
+
signing_key:
|
48
|
+
specification_version: 4
|
49
|
+
summary: Handy utility to massage MySQL dump files
|
50
|
+
test_files: []
|