fast_schema_dumper 0.5.3 → 0.5.5

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: 82c764033800133b7c3d6eb20f337a2fd500edfe7444e8e3a9cc19d652fa1cc1
4
+ data.tar.gz: 57404fe3266a39b3c09858e38fa67930fee4a19f8d42d9b2cf98a78ff0918603
5
5
  SHA512:
6
- metadata.gz: d4cf177c3a6e7dfa18bbf2018b28555beff7d58fbb2781f3a589850d9deaeb5203577aed31612098e15c5ed79240e3deb9f1de4b5ebbac5b992148f7b96c98d5
7
- data.tar.gz: 40779b25d4c21165b35af9cdb40900b40f4ad3bb1968d9fc85cc30ec50e4b78eb0b84254cd6e74bc5a504d78e257d33b0659a60aa6d92ebe977c219a55329225
6
+ metadata.gz: 737815e4794bc7edcb644f87eb572d7166eb49f37a57fe5e3b7b3bd82999aa9291491321afcafc98a67fd46aed222b859a807849e5a27986c0f1a7f20dba4f36
7
+ data.tar.gz: 1057a10843c1ab556dcdfe570a9798b8630b7ea8aab965658773802ff8d12fce00ab8849402bbc017c51dcedcfbe4d412bcee8a782c938394c1d5bba31b9cf68
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.5.5] - 2026-11-06
4
+
5
+ - Require Active Record 7.2 or later. [#57](https://github.com/smartbank-inc/fast_schema_dumper/pull/57)
6
+ - Test against Active Record 7.2, 8.0 and 8.1 in CI. [#57](https://github.com/smartbank-inc/fast_schema_dumper/pull/57)
7
+ - Fix column option order to dump `collation` before `comment`, matching
8
+ `ActiveRecord::SchemaDumper` output. [#65](https://github.com/smartbank-inc/fast_schema_dumper/pull/65)
9
+
10
+ ## [0.5.4] - 2026-08-04
11
+
12
+ - Support MySQL functional indexes (e.g. `INDEX ((LOWER(name)))`). Indexes
13
+ containing expression parts are now dumped as a single string column, matching
14
+ `ActiveRecord::SchemaDumper` output. Requires MySQL 8.0.13+; older versions
15
+ fall back to the previous behavior. [#55](https://github.com/smartbank-inc/fast_schema_dumper/pull/55)
16
+ - Index lines are now sorted by the formatted statement string, matching
17
+ `ActiveRecord::SchemaDumper` ordering exactly. [#55](https://github.com/smartbank-inc/fast_schema_dumper/pull/55)
18
+
3
19
  ## [0.5.3] - 2026-05-18
4
20
 
5
21
  - 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
  #
@@ -419,6 +435,15 @@ module FastSchemaDumper
419
435
  # null
420
436
  col_def += ", null: false" if column['IS_NULLABLE'] == 'NO'
421
437
 
438
+ # collation
439
+ if column['COLLATION_NAME'] && column['DATA_TYPE'] =~ /char|text/
440
+ # Check if it's different from the table's default collation
441
+ # For now, just check if it's utf8mb4_bin which seems to be the special case
442
+ if column['COLLATION_NAME'] == 'utf8mb4_bin'
443
+ col_def += ", collation: \"#{column['COLLATION_NAME']}\""
444
+ end
445
+ end
446
+
422
447
  # comment
423
448
  if column['COLUMN_COMMENT'] && !column['COLUMN_COMMENT'].empty?
424
449
  col_def += ", comment: \"#{escape_string(column['COLUMN_COMMENT'])}\""
@@ -429,15 +454,6 @@ module FastSchemaDumper
429
454
  col_def += ", unsigned: true"
430
455
  end
431
456
 
432
- # collation
433
- if column['COLLATION_NAME'] && column['DATA_TYPE'] =~ /char|text/
434
- # Check if it's different from the table's default collation
435
- # For now, just check if it's utf8mb4_bin which seems to be the special case
436
- if column['COLLATION_NAME'] == 'utf8mb4_bin'
437
- col_def += ", collation: \"#{column['COLLATION_NAME']}\""
438
- end
439
- end
440
-
441
457
  col_def
442
458
  end
443
459
 
@@ -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.5"
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.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daisuke Aritomo
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '0'
18
+ version: 7.2.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: '0'
25
+ version: 7.2.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: bigdecimal
28
28
  requirement: !ruby/object:Gem::Requirement