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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7d3a6108bbb29f59b67eac0c9e06b9e67824e926802366cedf34138fe7fea734
4
- data.tar.gz: 2c0e7a71656edb9832b024c97094ef0971c5a9ceb836f44225933f85d1821e82
3
+ metadata.gz: e66bdcea2398d20e0a2957851af140da62488eb5c4993e65131ea186d5ad9df6
4
+ data.tar.gz: d70566eea36f7e7dde2ac0b7aaf339e6353e642d82883d4497eedf73a91470be
5
5
  SHA512:
6
- metadata.gz: d4cf177c3a6e7dfa18bbf2018b28555beff7d58fbb2781f3a589850d9deaeb5203577aed31612098e15c5ed79240e3deb9f1de4b5ebbac5b992148f7b96c98d5
7
- data.tar.gz: 40779b25d4c21165b35af9cdb40900b40f4ad3bb1968d9fc85cc30ec50e4b78eb0b84254cd6e74bc5a504d78e257d33b0659a60aa6d92ebe977c219a55329225
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']][:columns] << idx['COLUMN_NAME']
85
- # Track descending order columns (COLLATION = 'D')
86
- if idx['COLLATION'] == 'D'
87
- hash[idx['TABLE_NAME']][idx['INDEX_NAME']][:orders][idx['COLUMN_NAME']] = :desc
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 orders indexes lexicographically by their column arrays
318
- # Example: ["a", "b"] < ["a"] < ["b", "c"] < ["b"] < ["d"]
319
- sorted_indexes = indexes.except('PRIMARY').sort_by do |index_name, index_data|
320
- # Create an array padded with high values for comparison
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|
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FastSchemaDumper
4
- VERSION = "0.5.3"
4
+ VERSION = "0.5.4"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fast_schema_dumper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.5.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daisuke Aritomo