exwiw 0.1.8 → 0.1.9
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/CHANGELOG.md +16 -0
- data/README.md +41 -1
- data/lib/exwiw/adapter/postgresql_adapter.rb +25 -0
- data/lib/exwiw/adapter.rb +4 -0
- data/lib/exwiw/cli.rb +20 -6
- data/lib/exwiw/runner.rb +25 -11
- data/lib/exwiw/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b48628d0a7599b151f957d6a40cd5a23cc3befa5007a86c550c834878b9893cc
|
|
4
|
+
data.tar.gz: 38fe41ada0a3e0a8f358bd60c3a28a7b7637e91bd2066d2e2a97e5542716540a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4105dbad3eb0291b841e7ebf152776913e19d817dbf3a9901a6838d7f16113c4fbd30887b88015fed735c0ec4b82c03367fafed81f2f6c7669c8f091da9b4041
|
|
7
|
+
data.tar.gz: 28a6383dbc953b93772f46adaf488cb4f0e826a0642859700c48536de46bde633a55780d8baff588da3e81dd6c4534d2fedd355424f7b68a70082c462eaf59aa
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.1.9] - 2026-05-21
|
|
6
|
+
|
|
7
|
+
Added
|
|
8
|
+
|
|
9
|
+
- PostgreSQL: --output-format=copy — emits COPY FROM stdin blocks instead of
|
|
10
|
+
INSERT statements. Faster restores for large dumps. (c026960)
|
|
11
|
+
- PostgreSQL: dump-all-tables mode — running without --target-table / --ids now
|
|
12
|
+
dumps every table in the scenario. (c026960)
|
|
13
|
+
|
|
14
|
+
Fixed
|
|
15
|
+
|
|
16
|
+
- PostgreSQL: value escape sequences — corrected escape handling in formatted
|
|
17
|
+
values. (50e6772)
|
|
18
|
+
|
|
19
|
+
https://github.com/heyinc/exwiw/pull/21
|
|
20
|
+
|
|
5
21
|
## [0.1.8] - 2026-05-16
|
|
6
22
|
|
|
7
23
|
### Added
|
data/README.md
CHANGED
|
@@ -62,6 +62,20 @@ exwiw \
|
|
|
62
62
|
--log-level=info
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
When `--target-table` and `--ids` are omitted, exwiw dumps all tables defined in `--config-dir`:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# dump all tables
|
|
69
|
+
exwiw \
|
|
70
|
+
--adapter=postgresql \
|
|
71
|
+
--host=localhost \
|
|
72
|
+
--port=5432 \
|
|
73
|
+
--user=reader \
|
|
74
|
+
--database=app_production \
|
|
75
|
+
--config-dir=exwiw \
|
|
76
|
+
--output-dir=dump
|
|
77
|
+
```
|
|
78
|
+
|
|
65
79
|
This command will generate sql files in the `dump` directory.
|
|
66
80
|
|
|
67
81
|
- `dump/insert-000-schema.sql` — idempotent `CREATE TABLE IF NOT EXISTS ...` for every table in scope. Apply this first to provision an empty database.
|
|
@@ -120,6 +134,32 @@ This is an example of the one table schema:
|
|
|
120
134
|
|
|
121
135
|
`--config-dir` will use all json files in the specified directory.
|
|
122
136
|
|
|
137
|
+
### Output format
|
|
138
|
+
|
|
139
|
+
By default, exwiw generates `INSERT` statements. For PostgreSQL, you can use `--output-format=copy` to generate `COPY FROM stdin` format instead, which is significantly faster for bulk loading:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
exwiw \
|
|
143
|
+
--adapter=postgresql \
|
|
144
|
+
--host=localhost \
|
|
145
|
+
--port=5432 \
|
|
146
|
+
--user=reader \
|
|
147
|
+
--database=app_production \
|
|
148
|
+
--config-dir=exwiw \
|
|
149
|
+
--target-table=shops \
|
|
150
|
+
--ids=1 \
|
|
151
|
+
--output-dir=dump \
|
|
152
|
+
--output-format=copy
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The generated file uses tab-separated values with PostgreSQL's text-format escaping (`\N` for NULL, `\\` for backslash, etc.). Import with `psql`:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
psql -d app_dev -f dump/insert-001-shops.sql
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`--output-format=copy` is only supported with the `postgresql` adapter.
|
|
162
|
+
|
|
123
163
|
### Bulk insert chunk size
|
|
124
164
|
|
|
125
165
|
`bulk_insert_chunk_size` splits the generated `INSERT` statement into multiple statements, each containing at most the specified number of rows. This is useful when the number of records per table is large enough to hit limits like MySQL's `max_allowed_packet`.
|
|
@@ -250,7 +290,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
|
250
290
|
|
|
251
291
|
## Contributing
|
|
252
292
|
|
|
253
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
|
293
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/heyinc/exwiw.
|
|
254
294
|
|
|
255
295
|
## License
|
|
256
296
|
|
|
@@ -74,6 +74,16 @@ module Exwiw
|
|
|
74
74
|
"INSERT INTO #{table_name} (#{column_names}) VALUES\n#{values};"
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
+
def to_copy_from_stdin(results, table)
|
|
78
|
+
column_names = table.columns.map(&:name).join(', ')
|
|
79
|
+
lines = ["COPY #{table.name} (#{column_names}) FROM stdin;"]
|
|
80
|
+
results.each do |row|
|
|
81
|
+
lines << row.map { |v| escape_copy_value(v) }.join("\t")
|
|
82
|
+
end
|
|
83
|
+
lines << '\\.'
|
|
84
|
+
lines.join("\n")
|
|
85
|
+
end
|
|
86
|
+
|
|
77
87
|
# Transcribe the FROM-side sequence cursor backing `table.primary_key`
|
|
78
88
|
# onto the import target. Without this, importing into a clean DB leaves
|
|
79
89
|
# the sequence at 1 while the inserted rows occupy higher IDs, so the
|
|
@@ -205,6 +215,21 @@ module Exwiw
|
|
|
205
215
|
end
|
|
206
216
|
end
|
|
207
217
|
|
|
218
|
+
private def escape_copy_value(value)
|
|
219
|
+
case value
|
|
220
|
+
when nil
|
|
221
|
+
"\\N"
|
|
222
|
+
when String
|
|
223
|
+
value
|
|
224
|
+
.gsub('\\') { '\\\\' }
|
|
225
|
+
.gsub("\t", '\t')
|
|
226
|
+
.gsub("\n", '\n')
|
|
227
|
+
.gsub("\r", '\r')
|
|
228
|
+
else
|
|
229
|
+
value.to_s
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
208
233
|
private def escape_single_quote(value)
|
|
209
234
|
value.gsub("'", "''")
|
|
210
235
|
end
|
data/lib/exwiw/adapter.rb
CHANGED
data/lib/exwiw/cli.rb
CHANGED
|
@@ -28,6 +28,7 @@ module Exwiw
|
|
|
28
28
|
@database_name = nil
|
|
29
29
|
@target_table_name = nil
|
|
30
30
|
@ids = []
|
|
31
|
+
@output_format = 'insert'
|
|
31
32
|
@log_level = :info
|
|
32
33
|
|
|
33
34
|
parser.parse!(@argv)
|
|
@@ -60,6 +61,7 @@ module Exwiw
|
|
|
60
61
|
output_dir: @output_dir,
|
|
61
62
|
config_dir: @config_dir,
|
|
62
63
|
dump_target: dump_target,
|
|
64
|
+
output_format: @output_format,
|
|
63
65
|
logger: logger,
|
|
64
66
|
).run
|
|
65
67
|
end
|
|
@@ -92,6 +94,17 @@ module Exwiw
|
|
|
92
94
|
exit 1
|
|
93
95
|
end
|
|
94
96
|
|
|
97
|
+
valid_output_formats = ["insert", "copy"]
|
|
98
|
+
unless valid_output_formats.include?(@output_format)
|
|
99
|
+
$stderr.puts "Invalid output format '#{@output_format}'. Available options are: #{valid_output_formats.join(', ')}"
|
|
100
|
+
exit 1
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
if @output_format == "copy" && @database_adapter != "postgresql"
|
|
104
|
+
$stderr.puts "--output-format=copy is only supported with the postgresql adapter"
|
|
105
|
+
exit 1
|
|
106
|
+
end
|
|
107
|
+
|
|
95
108
|
if @config_dir.nil?
|
|
96
109
|
$stderr.puts "Config dir is required"
|
|
97
110
|
exit 1
|
|
@@ -107,13 +120,13 @@ module Exwiw
|
|
|
107
120
|
exit 1
|
|
108
121
|
end
|
|
109
122
|
|
|
110
|
-
if @target_table_name
|
|
111
|
-
$stderr.puts "
|
|
123
|
+
if @target_table_name && @ids.empty?
|
|
124
|
+
$stderr.puts "--ids is required when --target-table is specified"
|
|
112
125
|
exit 1
|
|
113
126
|
end
|
|
114
127
|
|
|
115
|
-
if @ids.
|
|
116
|
-
$stderr.puts "
|
|
128
|
+
if !@target_table_name && @ids.any?
|
|
129
|
+
$stderr.puts "--target-table is required when --ids is specified"
|
|
117
130
|
exit 1
|
|
118
131
|
end
|
|
119
132
|
end
|
|
@@ -151,8 +164,9 @@ module Exwiw
|
|
|
151
164
|
end
|
|
152
165
|
opts.on("-a", "--adapter=ADAPTER", "Database adapter") { |v| @database_adapter = v }
|
|
153
166
|
opts.on("--database=DATABASE", "Target database name") { |v| @database_name = v }
|
|
154
|
-
opts.on("--target-table=TABLE", "Target table for extraction") { |v| @target_table_name = v }
|
|
155
|
-
opts.on("--ids=IDS", "Comma-separated list of identifiers") { |v| @ids = v.split(',') }
|
|
167
|
+
opts.on("--target-table=[TABLE]", "Target table for extraction. If omitted, dump all tables.") { |v| @target_table_name = v }
|
|
168
|
+
opts.on("--ids=[IDS]", "Comma-separated list of identifiers. Required when --target-table is given.") { |v| @ids = v.split(',') }
|
|
169
|
+
opts.on("--output-format=[FORMAT]", "Output format: insert (default) or copy (PostgreSQL only)") { |v| @output_format = v }
|
|
156
170
|
opts.on("--log-level=LEVEL", "Log level (debug, info). default is info") { |v| @log_level = v.to_sym }
|
|
157
171
|
|
|
158
172
|
opts.on("--help", "Print this help") do
|
data/lib/exwiw/runner.rb
CHANGED
|
@@ -9,12 +9,14 @@ module Exwiw
|
|
|
9
9
|
output_dir:,
|
|
10
10
|
config_dir:,
|
|
11
11
|
dump_target:,
|
|
12
|
-
logger
|
|
12
|
+
logger:,
|
|
13
|
+
output_format: 'insert'
|
|
13
14
|
)
|
|
14
15
|
@connection_config = connection_config
|
|
15
16
|
@output_dir = output_dir
|
|
16
17
|
@config_dir = config_dir
|
|
17
18
|
@dump_target = dump_target
|
|
19
|
+
@output_format = output_format
|
|
18
20
|
@logger = logger
|
|
19
21
|
end
|
|
20
22
|
|
|
@@ -52,18 +54,30 @@ module Exwiw
|
|
|
52
54
|
@logger.info(" No records matched. skip this table.")
|
|
53
55
|
next
|
|
54
56
|
end
|
|
55
|
-
|
|
57
|
+
insert_idx = (idx + 1).to_s.rjust(3, '0')
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
if @output_format == 'copy'
|
|
60
|
+
@logger.debug(" Generate COPY statement...")
|
|
61
|
+
copy_sql = adapter.to_copy_from_stdin(results, table)
|
|
62
|
+
@logger.info(" Generated COPY statement for #{record_num} records.")
|
|
60
63
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
File.open(File.join(@output_dir, "insert-#{insert_idx}-#{table_name}.#{adapter.output_extension}"), 'w') do |file|
|
|
65
|
+
file.puts(copy_sql)
|
|
66
|
+
post = adapter.post_insert_sql(table)
|
|
67
|
+
file.puts(post) if post
|
|
68
|
+
end
|
|
69
|
+
else
|
|
70
|
+
@logger.debug(" Generate INSERT statement...")
|
|
71
|
+
chunk_size = table.bulk_insert_chunk_size
|
|
72
|
+
chunks = chunk_size ? results.each_slice(chunk_size).to_a : [results]
|
|
73
|
+
insert_sql = chunks.map { |chunk_rows| adapter.to_bulk_insert(chunk_rows, table) }.join("\n")
|
|
74
|
+
|
|
75
|
+
@logger.info(" Generated INSERT statement for #{record_num} records (#{chunks.size} statement(s)).")
|
|
76
|
+
File.open(File.join(@output_dir, "insert-#{insert_idx}-#{table_name}.#{adapter.output_extension}"), 'w') do |file|
|
|
77
|
+
file.puts(insert_sql)
|
|
78
|
+
post = adapter.post_insert_sql(table)
|
|
79
|
+
file.puts(post) if post
|
|
80
|
+
end
|
|
67
81
|
end
|
|
68
82
|
|
|
69
83
|
if adapter.supports_bulk_delete?
|
data/lib/exwiw/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: exwiw
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shia
|
|
@@ -83,7 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
83
83
|
- !ruby/object:Gem::Version
|
|
84
84
|
version: '0'
|
|
85
85
|
requirements: []
|
|
86
|
-
rubygems_version:
|
|
86
|
+
rubygems_version: 3.6.9
|
|
87
87
|
specification_version: 4
|
|
88
88
|
summary: Ruby gem that allows you to export records from a database to a dump file.
|
|
89
89
|
test_files: []
|