mysql-to-sqlite 0.1.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 +7 -0
- data/LICENSE.txt +15 -0
- data/README.md +107 -0
- data/bin/mysql-to-sqlite +41 -0
- data/lib/mysql_to_sqlite/importer.rb +237 -0
- data/lib/mysql_to_sqlite/version.rb +3 -0
- data/lib/mysql_to_sqlite.rb +4 -0
- metadata +94 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 1ab0d1cf766d8bdd6c149e69adc043a54e43f1c8e260c749004d03a03e50c8e3
|
|
4
|
+
data.tar.gz: 46b9f74caa805c880d96d48e9ce91560d8143a64b93795a02733e1ec20adf9f0
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 64e7c1ed0fc10ebedeb593cf55aba2ca0630eebaae6b12543476d7f7d93469986e99b40058add4bc966dad96507a5437e84df92ce021bd2fea5efde4f5a3e37e
|
|
7
|
+
data.tar.gz: da9e356fb107326a0ef4b5ac80b393bd42956cc299c2048563c28dc0978c5af2158cedeb9525636eafaffd814257c862195ed0c76c5eed1170d472b7168cf98b
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dean
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
|
6
|
+
software and associated documentation files (the "Software"), to deal in the Software
|
|
7
|
+
without restriction, including without limitation the rights to use, copy, modify,
|
|
8
|
+
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to the following
|
|
10
|
+
conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all copies
|
|
13
|
+
or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.
|
data/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# mysql-to-sqlite
|
|
2
|
+
|
|
3
|
+
[](https://github.com/deanpcmad/mysql-to-sqlite/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
A standalone, schema-aware copier that copies raw table data from MySQL to an existing
|
|
6
|
+
SQLite database through Active Record, without loading an application's models or
|
|
7
|
+
running callbacks.
|
|
8
|
+
|
|
9
|
+
The source and destination must have matching table columns and the same maximum
|
|
10
|
+
`schema_migrations.version`. The importer refuses a non-empty destination by default,
|
|
11
|
+
copies in batches, and validates row counts, foreign keys, and integer sequences.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
### Requirements
|
|
16
|
+
|
|
17
|
+
- Ruby 3.3 or newer
|
|
18
|
+
- MySQL or MariaDB client libraries and development headers, required by `mysql2`
|
|
19
|
+
- SQLite libraries and development headers, required by `sqlite3`
|
|
20
|
+
|
|
21
|
+
The package names vary by operating system. Install the development packages provided
|
|
22
|
+
by your system package manager before installing the gem.
|
|
23
|
+
|
|
24
|
+
### Install the command
|
|
25
|
+
|
|
26
|
+
Install the published gem for standalone use:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
gem install mysql-to-sqlite
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Confirm that the command is available:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
mysql-to-sqlite --version
|
|
36
|
+
mysql-to-sqlite --help
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Add it to an application
|
|
40
|
+
|
|
41
|
+
Add the gem to the application's development dependencies:
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
group :development do
|
|
45
|
+
gem "mysql-to-sqlite"
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Install the bundle and run the command through Bundler:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
bundle install
|
|
53
|
+
bundle exec mysql-to-sqlite --help
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Use
|
|
57
|
+
|
|
58
|
+
First create or migrate an empty SQLite database to exactly the schema version recorded
|
|
59
|
+
in MySQL. Then run:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
mysql-to-sqlite \
|
|
63
|
+
--source 'mysql2://user:password@127.0.0.1/database' \
|
|
64
|
+
--destination /absolute/path/to/database.sqlite3 \
|
|
65
|
+
--yes
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The URLs may instead be supplied as `MYSQL_SOURCE_URL` and
|
|
69
|
+
`SQLITE_DATABASE_PATH`. Run `mysql-to-sqlite --help` for all options, or prefix the
|
|
70
|
+
command with `bundle exec` when it is installed through an application's bundle.
|
|
71
|
+
|
|
72
|
+
`--replace` deletes records from matching destination tables before copying.
|
|
73
|
+
`--ignore-source-only` skips tables found only in MySQL; use it only after inspecting
|
|
74
|
+
why the schemas differ.
|
|
75
|
+
|
|
76
|
+
Stop all writes during the final import and keep verified backups. This tool copies
|
|
77
|
+
database records only; application uploads and encryption keys must be preserved
|
|
78
|
+
separately.
|
|
79
|
+
|
|
80
|
+
## Test
|
|
81
|
+
|
|
82
|
+
The unit tests do not require a database server:
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
bundle exec rake test
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The integration suite restores the SQL dumps under `test/fixtures/mysql` into a
|
|
89
|
+
disposable MySQL database and imports them into temporary SQLite files. Start the
|
|
90
|
+
provided MySQL 8.4 service and opt into those tests:
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
docker compose -f compose.test.yml up --detach --wait
|
|
94
|
+
|
|
95
|
+
MYSQL_TEST_URL='mysql2://root:test-password@127.0.0.1:3307/mysql_to_sqlite_test' \
|
|
96
|
+
bundle exec rake test
|
|
97
|
+
|
|
98
|
+
docker compose -f compose.test.yml down
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Set `MYSQL_TEST_PORT` on both Compose commands and use the same port in
|
|
102
|
+
`MYSQL_TEST_URL` when port 3307 is unavailable.
|
|
103
|
+
|
|
104
|
+
The fixtures cover relationships, explicit primary keys and sequences, Unicode,
|
|
105
|
+
binary and null values, replacement mode, source-only tables, schema mismatches, and
|
|
106
|
+
migration-version mismatches. When `MYSQL_TEST_URL` is absent, integration cases are
|
|
107
|
+
reported as skipped.
|
data/bin/mysql-to-sqlite
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
|
|
5
|
+
options = {
|
|
6
|
+
batch_size: ENV.fetch("IMPORT_BATCH_SIZE", 1_000),
|
|
7
|
+
destination: ENV["SQLITE_DATABASE_PATH"],
|
|
8
|
+
source_url: ENV["MYSQL_SOURCE_URL"]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
parser = OptionParser.new do |opts|
|
|
12
|
+
opts.banner = "Usage: mysql-to-sqlite --source MYSQL_URL --destination FILE --yes"
|
|
13
|
+
opts.on("--source URL", "MySQL URL (or MYSQL_SOURCE_URL)") { |url| options[:source_url] = url }
|
|
14
|
+
opts.on("--destination FILE", "Existing SQLite database (or SQLITE_DATABASE_PATH)") { |file| options[:destination] = file }
|
|
15
|
+
opts.on("--batch-size N", Integer, "Rows copied per batch") { |size| options[:batch_size] = size }
|
|
16
|
+
opts.on("--ignore-source-only", "Skip tables that exist only in MySQL") { options[:ignore_source_only] = true }
|
|
17
|
+
opts.on("--replace", "Clear destination records before importing") { options[:replace] = true }
|
|
18
|
+
opts.on("--yes", "Confirm the import") { options[:confirmed] = true }
|
|
19
|
+
opts.on("-v", "--version", "Show version") do
|
|
20
|
+
require_relative "../lib/mysql_to_sqlite/version"
|
|
21
|
+
puts MysqlToSqlite::VERSION
|
|
22
|
+
exit
|
|
23
|
+
end
|
|
24
|
+
opts.on("-h", "--help", "Show this help") do
|
|
25
|
+
puts opts
|
|
26
|
+
exit
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
parser.parse!
|
|
30
|
+
|
|
31
|
+
abort "Pass --source or set MYSQL_SOURCE_URL" if options[:source_url].to_s.empty?
|
|
32
|
+
abort "Pass --destination or set SQLITE_DATABASE_PATH" if options[:destination].to_s.empty?
|
|
33
|
+
abort "Destination does not exist: #{options[:destination]}" unless File.file?(options[:destination])
|
|
34
|
+
abort "Pass --yes after verifying the destination path and backups" unless options[:confirmed]
|
|
35
|
+
|
|
36
|
+
require_relative "../lib/mysql_to_sqlite"
|
|
37
|
+
|
|
38
|
+
puts "Importing MySQL data into #{File.expand_path(options[:destination])}"
|
|
39
|
+
MysqlToSqlite::Importer.new(**options.slice(
|
|
40
|
+
:source_url, :destination, :batch_size, :ignore_source_only, :replace
|
|
41
|
+
)).run!
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
module MysqlToSqlite
|
|
2
|
+
class Importer
|
|
3
|
+
class SourceRecord < ActiveRecord::Base
|
|
4
|
+
self.abstract_class = true
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
class TargetRecord < ActiveRecord::Base
|
|
8
|
+
self.abstract_class = true
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
INTERNAL_TABLES = %w[ar_internal_metadata schema_migrations].freeze
|
|
12
|
+
|
|
13
|
+
def initialize(source_url:, destination:, batch_size: 1_000, replace: false,
|
|
14
|
+
ignore_source_only: false, output: $stdout)
|
|
15
|
+
@source_url = source_url
|
|
16
|
+
@destination = destination
|
|
17
|
+
@batch_size = Integer(batch_size)
|
|
18
|
+
raise ArgumentError, "batch size must be positive" unless @batch_size.positive?
|
|
19
|
+
|
|
20
|
+
@replace = replace
|
|
21
|
+
@ignore_source_only = ignore_source_only
|
|
22
|
+
@output = output
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def run!
|
|
26
|
+
connect!
|
|
27
|
+
verify_databases!
|
|
28
|
+
discover_tables!
|
|
29
|
+
verify_migration_versions!
|
|
30
|
+
verify_table_sets!
|
|
31
|
+
verify_table_schemas!
|
|
32
|
+
report_plan
|
|
33
|
+
|
|
34
|
+
without_target_foreign_keys do
|
|
35
|
+
TargetRecord.transaction do
|
|
36
|
+
clear_target! if @replace
|
|
37
|
+
verify_target_is_empty!
|
|
38
|
+
reset_target_sequences!
|
|
39
|
+
tables.each { |table| copy_table(table) }
|
|
40
|
+
validate!
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
output.puts "Import completed successfully."
|
|
45
|
+
ensure
|
|
46
|
+
SourceRecord.connection_pool.disconnect! if SourceRecord.connected?
|
|
47
|
+
TargetRecord.connection_pool.disconnect! if TargetRecord.connected?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
attr_reader :batch_size, :output, :source, :tables, :target
|
|
53
|
+
|
|
54
|
+
def connect!
|
|
55
|
+
require "mysql2"
|
|
56
|
+
require "sqlite3"
|
|
57
|
+
|
|
58
|
+
SourceRecord.establish_connection(@source_url)
|
|
59
|
+
TargetRecord.establish_connection(adapter: "sqlite3", database: @destination)
|
|
60
|
+
@source = SourceRecord.connection
|
|
61
|
+
@target = TargetRecord.connection
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def verify_databases!
|
|
65
|
+
raise ArgumentError, "source URL must use the mysql2 adapter" unless source.adapter_name == "Mysql2"
|
|
66
|
+
raise "The destination database must use SQLite" unless target.adapter_name == "SQLite"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def discover_tables!
|
|
70
|
+
@source_tables = source.data_sources - INTERNAL_TABLES
|
|
71
|
+
@target_tables = target.data_sources - INTERNAL_TABLES
|
|
72
|
+
@tables = (@source_tables & @target_tables).sort
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def verify_table_sets!
|
|
76
|
+
source_only = @source_tables - @target_tables
|
|
77
|
+
target_only = @target_tables - @source_tables
|
|
78
|
+
|
|
79
|
+
if source_only.any? && !@ignore_source_only
|
|
80
|
+
raise compact(<<~MESSAGE)
|
|
81
|
+
Source-only tables found: #{source_only.join(", ")}.
|
|
82
|
+
Prepare the destination at the source migration version, or pass
|
|
83
|
+
--ignore-source-only after verifying these tables can be skipped.
|
|
84
|
+
MESSAGE
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
if target_only.any?
|
|
88
|
+
raise compact(<<~MESSAGE)
|
|
89
|
+
Destination-only tables found: #{target_only.join(", ")}.
|
|
90
|
+
Prepare the destination at the same migration version as the source.
|
|
91
|
+
MESSAGE
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
raise "No application tables were found to import" if tables.empty?
|
|
95
|
+
output.puts "Skipping source-only tables: #{source_only.join(", ")}" if source_only.any?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def verify_table_schemas!
|
|
99
|
+
mismatches = tables.filter_map do |table|
|
|
100
|
+
source_columns = source.columns(table).map(&:name)
|
|
101
|
+
target_columns = target.columns(table).map(&:name)
|
|
102
|
+
next if source_columns.sort == target_columns.sort
|
|
103
|
+
|
|
104
|
+
missing = target_columns - source_columns
|
|
105
|
+
extra = source_columns - target_columns
|
|
106
|
+
"#{table} (missing: #{missing.inspect}, extra: #{extra.inspect})"
|
|
107
|
+
end
|
|
108
|
+
raise "Schema mismatch: #{mismatches.join("; ")}" if mismatches.any?
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def verify_migration_versions!
|
|
112
|
+
source_version = migration_version(source)
|
|
113
|
+
target_version = migration_version(target)
|
|
114
|
+
return if source_version == target_version
|
|
115
|
+
|
|
116
|
+
raise compact(<<~MESSAGE)
|
|
117
|
+
Migration versions differ (source: #{source_version || "unknown"},
|
|
118
|
+
destination: #{target_version || "unknown"}). Prepare the destination at the
|
|
119
|
+
source version before importing.
|
|
120
|
+
MESSAGE
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def report_plan
|
|
124
|
+
output.puts "Source migration version: #{migration_version(source) || "unknown"}"
|
|
125
|
+
output.puts "Destination migration version: #{migration_version(target) || "unknown"}"
|
|
126
|
+
output.puts "Tables to copy: #{tables.join(", ")}"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def migration_version(connection)
|
|
130
|
+
return unless connection.data_source_exists?("schema_migrations")
|
|
131
|
+
|
|
132
|
+
connection.select_value("SELECT MAX(version) FROM schema_migrations")
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def without_target_foreign_keys
|
|
136
|
+
target.execute("PRAGMA foreign_keys = OFF")
|
|
137
|
+
yield
|
|
138
|
+
ensure
|
|
139
|
+
target.execute("PRAGMA foreign_keys = ON") if target
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def verify_target_is_empty!
|
|
143
|
+
populated = tables.filter_map do |table|
|
|
144
|
+
count = target_count(table)
|
|
145
|
+
"#{table}=#{count}" if count.positive?
|
|
146
|
+
end
|
|
147
|
+
raise "Destination must be empty before import (#{populated.join(", ")})" if populated.any?
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def clear_target!
|
|
151
|
+
tables.reverse_each do |table|
|
|
152
|
+
target.execute("DELETE FROM #{target.quote_table_name(table)}")
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def reset_target_sequences!
|
|
157
|
+
return unless sqlite_sequence_table?
|
|
158
|
+
|
|
159
|
+
names = tables.map { |table| target.quote(table) }.join(", ")
|
|
160
|
+
target.execute("DELETE FROM sqlite_sequence WHERE name IN (#{names})")
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def copy_table(table)
|
|
164
|
+
columns = source.columns(table).map(&:name)
|
|
165
|
+
record_class = Class.new(TargetRecord) do
|
|
166
|
+
self.table_name = table
|
|
167
|
+
self.inheritance_column = :_type_disabled
|
|
168
|
+
end
|
|
169
|
+
copied = 0
|
|
170
|
+
order = source.primary_key(table) || columns.first
|
|
171
|
+
|
|
172
|
+
loop do
|
|
173
|
+
rows = source.select_all(compact(<<~SQL)).to_a
|
|
174
|
+
SELECT #{columns.map { |column| source.quote_column_name(column) }.join(", ")}
|
|
175
|
+
FROM #{source.quote_table_name(table)}
|
|
176
|
+
ORDER BY #{source.quote_column_name(order)}
|
|
177
|
+
LIMIT #{batch_size} OFFSET #{copied}
|
|
178
|
+
SQL
|
|
179
|
+
break if rows.empty?
|
|
180
|
+
|
|
181
|
+
record_class.insert_all!(rows, record_timestamps: false)
|
|
182
|
+
copied += rows.length
|
|
183
|
+
output.puts "#{table}: copied #{copied}" if rows.length == batch_size
|
|
184
|
+
end
|
|
185
|
+
output.puts "#{table}: #{copied} row(s)"
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def validate!
|
|
189
|
+
failures = tables.filter_map do |table|
|
|
190
|
+
source_count = source.select_value(
|
|
191
|
+
"SELECT COUNT(*) FROM #{source.quote_table_name(table)}"
|
|
192
|
+
).to_i
|
|
193
|
+
destination_count = target_count(table)
|
|
194
|
+
"#{table}: source=#{source_count}, destination=#{destination_count}" unless source_count == destination_count
|
|
195
|
+
end
|
|
196
|
+
raise "Row-count validation failed (#{failures.join("; ")})" if failures.any?
|
|
197
|
+
|
|
198
|
+
violations = target.select_all("PRAGMA foreign_key_check")
|
|
199
|
+
raise "Foreign-key validation failed: #{violations.to_a.inspect}" if violations.any?
|
|
200
|
+
|
|
201
|
+
validate_sequences!
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def validate_sequences!
|
|
205
|
+
return unless sqlite_sequence_table?
|
|
206
|
+
|
|
207
|
+
tables.each do |table|
|
|
208
|
+
primary_key = target.primary_key(table)
|
|
209
|
+
column = target.columns(table).find { |candidate| candidate.name == primary_key }
|
|
210
|
+
next unless column&.type == :integer
|
|
211
|
+
|
|
212
|
+
max_id = target.select_value(
|
|
213
|
+
"SELECT MAX(#{target.quote_column_name(primary_key)}) FROM #{target.quote_table_name(table)}"
|
|
214
|
+
).to_i
|
|
215
|
+
sequence = target.select_value(
|
|
216
|
+
"SELECT seq FROM sqlite_sequence WHERE name = #{target.quote(table)}"
|
|
217
|
+
).to_i
|
|
218
|
+
raise "Sequence validation failed for #{table}" unless max_id == sequence
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def target_count(table)
|
|
223
|
+
target.select_value("SELECT COUNT(*) FROM #{target.quote_table_name(table)}").to_i
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def sqlite_sequence_table?
|
|
227
|
+
target.select_value(<<~SQL)&.to_s&.length&.positive?
|
|
228
|
+
SELECT name FROM sqlite_master
|
|
229
|
+
WHERE type = 'table' AND name = 'sqlite_sequence'
|
|
230
|
+
SQL
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def compact(string)
|
|
234
|
+
string.lines.map(&:strip).join(" ")
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: mysql-to-sqlite
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Dean Perry
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activerecord
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.2'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '9'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '7.2'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '9'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: mysql2
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - "~>"
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '0.5'
|
|
39
|
+
type: :runtime
|
|
40
|
+
prerelease: false
|
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - "~>"
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '0.5'
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: sqlite3
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - ">="
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '2.1'
|
|
53
|
+
type: :runtime
|
|
54
|
+
prerelease: false
|
|
55
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '2.1'
|
|
60
|
+
description: A schema-aware MySQL-to-SQLite data importer built on Active Record.
|
|
61
|
+
email:
|
|
62
|
+
- dean@deanpcmad.com
|
|
63
|
+
executables:
|
|
64
|
+
- mysql-to-sqlite
|
|
65
|
+
extensions: []
|
|
66
|
+
extra_rdoc_files: []
|
|
67
|
+
files:
|
|
68
|
+
- LICENSE.txt
|
|
69
|
+
- README.md
|
|
70
|
+
- bin/mysql-to-sqlite
|
|
71
|
+
- lib/mysql_to_sqlite.rb
|
|
72
|
+
- lib/mysql_to_sqlite/importer.rb
|
|
73
|
+
- lib/mysql_to_sqlite/version.rb
|
|
74
|
+
licenses:
|
|
75
|
+
- MIT
|
|
76
|
+
metadata: {}
|
|
77
|
+
rdoc_options: []
|
|
78
|
+
require_paths:
|
|
79
|
+
- lib
|
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
81
|
+
requirements:
|
|
82
|
+
- - ">="
|
|
83
|
+
- !ruby/object:Gem::Version
|
|
84
|
+
version: '3.3'
|
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
requirements: []
|
|
91
|
+
rubygems_version: 4.0.16
|
|
92
|
+
specification_version: 4
|
|
93
|
+
summary: Copy a version-matched MySQL database into SQLite
|
|
94
|
+
test_files: []
|