fast_schema_dumper 0.5.3 → 0.5.4
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 +9 -0
- data/lib/fast_schema_dumper/fast_dumper.rb +70 -18
- data/lib/fast_schema_dumper/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e66bdcea2398d20e0a2957851af140da62488eb5c4993e65131ea186d5ad9df6
|
|
4
|
+
data.tar.gz: d70566eea36f7e7dde2ac0b7aaf339e6353e642d82883d4497eedf73a91470be
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 670c4795780e2ab759a9064030432b2e97d7e5ae5031988b8197b427bf03b011d4c9aa028dc7354233b9af6b213ba651339135874202f71c83152fcdb6a11678
|
|
7
|
+
data.tar.gz: e382f5cab4f77f165b200891d85ecf9642c0aabbe9601d5260e3eb373ba26bb9c6b850dcaa1536dcdfbdfb88c273d321938e1003f9f5a8dd11a49e2a81b51252
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.5.4] - 2026-08-04
|
|
4
|
+
|
|
5
|
+
- Support MySQL functional indexes (e.g. `INDEX ((LOWER(name)))`). Indexes
|
|
6
|
+
containing expression parts are now dumped as a single string column, matching
|
|
7
|
+
`ActiveRecord::SchemaDumper` output. Requires MySQL 8.0.13+; older versions
|
|
8
|
+
fall back to the previous behavior. [#55](https://github.com/smartbank-inc/fast_schema_dumper/pull/55)
|
|
9
|
+
- Index lines are now sorted by the formatted statement string, matching
|
|
10
|
+
`ActiveRecord::SchemaDumper` ordering exactly. [#55](https://github.com/smartbank-inc/fast_schema_dumper/pull/55)
|
|
11
|
+
|
|
3
12
|
## [0.5.3] - 2026-05-18
|
|
4
13
|
|
|
5
14
|
- Add limit handling for binary/varbinary columns in format_column. [#39](https://github.com/smartbank-inc/fast_schema_dumper/pull/39)
|
|
@@ -49,6 +49,16 @@ module FastSchemaDumper
|
|
|
49
49
|
")
|
|
50
50
|
|
|
51
51
|
# Get all indexes
|
|
52
|
+
# INFORMATION_SCHEMA.STATISTICS.EXPRESSION exists only in MySQL 8.0.13+
|
|
53
|
+
result = conn.exec_query("
|
|
54
|
+
SELECT COUNT(*) as count
|
|
55
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
56
|
+
WHERE TABLE_SCHEMA = 'information_schema'
|
|
57
|
+
AND TABLE_NAME = 'STATISTICS'
|
|
58
|
+
AND COLUMN_NAME = 'EXPRESSION'
|
|
59
|
+
")
|
|
60
|
+
expression_column = (result.first['count'] > 0) ? "s.EXPRESSION," : ""
|
|
61
|
+
|
|
52
62
|
indexes_data = conn.exec_query("
|
|
53
63
|
SELECT
|
|
54
64
|
s.TABLE_NAME,
|
|
@@ -56,6 +66,8 @@ module FastSchemaDumper
|
|
|
56
66
|
s.NON_UNIQUE,
|
|
57
67
|
s.COLUMN_NAME,
|
|
58
68
|
s.SEQ_IN_INDEX,
|
|
69
|
+
s.SUB_PART,
|
|
70
|
+
#{expression_column}
|
|
59
71
|
s.INDEX_COMMENT,
|
|
60
72
|
s.COLLATION
|
|
61
73
|
FROM INFORMATION_SCHEMA.STATISTICS s
|
|
@@ -69,6 +81,7 @@ module FastSchemaDumper
|
|
|
69
81
|
hash[idx['TABLE_NAME']] ||= {}
|
|
70
82
|
hash[idx['TABLE_NAME']][idx['INDEX_NAME']] ||= {
|
|
71
83
|
columns: [],
|
|
84
|
+
parts: [],
|
|
72
85
|
unique: idx['NON_UNIQUE'] == 0,
|
|
73
86
|
# length
|
|
74
87
|
orders: {},
|
|
@@ -81,10 +94,22 @@ module FastSchemaDumper
|
|
|
81
94
|
comment: idx['INDEX_COMMENT']
|
|
82
95
|
# enabled
|
|
83
96
|
}
|
|
84
|
-
hash[idx['TABLE_NAME']][idx['INDEX_NAME']]
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
97
|
+
index = hash[idx['TABLE_NAME']][idx['INDEX_NAME']]
|
|
98
|
+
desc = idx['COLLATION'] == 'D'
|
|
99
|
+
|
|
100
|
+
# Functional index parts (MySQL 8.0.13+) have COLUMN_NAME = NULL and the
|
|
101
|
+
# expression in EXPRESSION
|
|
102
|
+
if idx['EXPRESSION']
|
|
103
|
+
index[:parts] << {expression: idx['EXPRESSION'], desc:}
|
|
104
|
+
else
|
|
105
|
+
# :columns/:orders are for expression-free indexes.
|
|
106
|
+
index[:columns] << idx['COLUMN_NAME']
|
|
107
|
+
# Track descending order columns (COLLATION = 'D')
|
|
108
|
+
index[:orders][idx['COLUMN_NAME']] = :desc if desc
|
|
109
|
+
|
|
110
|
+
# Also keep the part in :parts so mixed indexes like
|
|
111
|
+
# ((MONTH(starts_at)) DESC, name) can be rebuilt from :parts alone.
|
|
112
|
+
index[:parts] << {column: idx['COLUMN_NAME'], sub_part: idx['SUB_PART'], desc:}
|
|
88
113
|
end
|
|
89
114
|
end
|
|
90
115
|
|
|
@@ -314,21 +339,12 @@ module FastSchemaDumper
|
|
|
314
339
|
end
|
|
315
340
|
|
|
316
341
|
# Indexes
|
|
317
|
-
# Rails
|
|
318
|
-
#
|
|
319
|
-
|
|
320
|
-
#
|
|
321
|
-
# This ensures that missing columns sort after existing ones
|
|
322
|
-
max_cols = indexes.values.map { |data| data[:columns].size }.max || 1
|
|
323
|
-
cols = index_data[:columns].dup
|
|
324
|
-
# Pad with a string that sorts after any real column name
|
|
325
|
-
cols += ["\xFF" * 100] * (max_cols - cols.size)
|
|
326
|
-
cols
|
|
327
|
-
end
|
|
328
|
-
|
|
329
|
-
sorted_indexes.each do |index_name, index_data|
|
|
330
|
-
@output << " #{format_index(index_name, index_data)}"
|
|
342
|
+
# Rails sorts the formatted index statements as strings
|
|
343
|
+
# ref: activerecord: lib/active_record/schema_dumper.rb#indexes_in_create
|
|
344
|
+
index_lines = indexes.except('PRIMARY').map do |index_name, index_data|
|
|
345
|
+
" #{format_index(index_name, index_data)}"
|
|
331
346
|
end
|
|
347
|
+
@output.concat(index_lines.sort)
|
|
332
348
|
|
|
333
349
|
# Respect the CHECK constraint
|
|
334
350
|
#
|
|
@@ -525,6 +541,11 @@ module FastSchemaDumper
|
|
|
525
541
|
end
|
|
526
542
|
|
|
527
543
|
def format_index(index_name, index_data)
|
|
544
|
+
parts = index_data[:parts] || []
|
|
545
|
+
if parts.any? { |part| part[:expression] }
|
|
546
|
+
return format_expression_index(index_name, index_data)
|
|
547
|
+
end
|
|
548
|
+
|
|
528
549
|
idx_def = "t.index "
|
|
529
550
|
|
|
530
551
|
idx_def += if index_data[:columns].size == 1
|
|
@@ -563,6 +584,37 @@ module FastSchemaDumper
|
|
|
563
584
|
idx_def
|
|
564
585
|
end
|
|
565
586
|
|
|
587
|
+
# Rails dumps indexes containing expression parts with a single string
|
|
588
|
+
# instead of a column name array. DESC and prefix lengths are inlined
|
|
589
|
+
# into the string, so no order:/length: options are emitted.
|
|
590
|
+
# ref: activerecord: lib/active_record/connection_adapters/mysql/schema_statements.rb#indexes
|
|
591
|
+
def format_expression_index(index_name, index_data)
|
|
592
|
+
columns_string = index_data[:parts].map { |part| format_index_part(part) }.join(", ")
|
|
593
|
+
|
|
594
|
+
idx_def = "t.index #{columns_string.inspect}"
|
|
595
|
+
idx_def += ", name: \"#{index_name}\""
|
|
596
|
+
idx_def += ", unique: true" if index_data[:unique]
|
|
597
|
+
|
|
598
|
+
if index_data[:comment] && !index_data[:comment].empty?
|
|
599
|
+
idx_def += ", comment: \"#{escape_string(index_data[:comment])}\""
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
idx_def
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
def format_index_part(part)
|
|
606
|
+
str = if part[:expression]
|
|
607
|
+
expression = part[:expression].gsub("\\'", "'")
|
|
608
|
+
expression.start_with?("(") ? expression : "(#{expression})"
|
|
609
|
+
else
|
|
610
|
+
quoted = "`#{part[:column].gsub("`", "``")}`"
|
|
611
|
+
quoted += "(#{part[:sub_part]})" if part[:sub_part]
|
|
612
|
+
quoted
|
|
613
|
+
end
|
|
614
|
+
str += " DESC" if part[:desc]
|
|
615
|
+
str
|
|
616
|
+
end
|
|
617
|
+
|
|
566
618
|
def balanced_parentheses?(str)
|
|
567
619
|
depth = 0
|
|
568
620
|
str.each_char do |char|
|