better_structure_sql 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.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +41 -0
  3. data/LICENSE +21 -0
  4. data/README.md +557 -0
  5. data/app/controllers/better_structure_sql/application_controller.rb +61 -0
  6. data/app/controllers/better_structure_sql/schema_versions_controller.rb +243 -0
  7. data/app/helpers/better_structure_sql/schema_versions_helper.rb +46 -0
  8. data/app/views/better_structure_sql/schema_versions/index.html.erb +110 -0
  9. data/app/views/better_structure_sql/schema_versions/show.html.erb +186 -0
  10. data/app/views/layouts/better_structure_sql/application.html.erb +105 -0
  11. data/config/database.yml +3 -0
  12. data/config/routes.rb +12 -0
  13. data/lib/better_structure_sql/adapters/base_adapter.rb +234 -0
  14. data/lib/better_structure_sql/adapters/mysql_adapter.rb +476 -0
  15. data/lib/better_structure_sql/adapters/mysql_config.rb +32 -0
  16. data/lib/better_structure_sql/adapters/postgresql_adapter.rb +646 -0
  17. data/lib/better_structure_sql/adapters/postgresql_config.rb +25 -0
  18. data/lib/better_structure_sql/adapters/registry.rb +115 -0
  19. data/lib/better_structure_sql/adapters/sqlite_adapter.rb +644 -0
  20. data/lib/better_structure_sql/adapters/sqlite_config.rb +26 -0
  21. data/lib/better_structure_sql/configuration.rb +129 -0
  22. data/lib/better_structure_sql/database_version.rb +46 -0
  23. data/lib/better_structure_sql/dependency_resolver.rb +63 -0
  24. data/lib/better_structure_sql/dumper.rb +544 -0
  25. data/lib/better_structure_sql/engine.rb +28 -0
  26. data/lib/better_structure_sql/file_writer.rb +180 -0
  27. data/lib/better_structure_sql/formatter.rb +70 -0
  28. data/lib/better_structure_sql/generators/base.rb +33 -0
  29. data/lib/better_structure_sql/generators/domain_generator.rb +22 -0
  30. data/lib/better_structure_sql/generators/extension_generator.rb +23 -0
  31. data/lib/better_structure_sql/generators/foreign_key_generator.rb +43 -0
  32. data/lib/better_structure_sql/generators/function_generator.rb +33 -0
  33. data/lib/better_structure_sql/generators/index_generator.rb +50 -0
  34. data/lib/better_structure_sql/generators/materialized_view_generator.rb +31 -0
  35. data/lib/better_structure_sql/generators/pragma_generator.rb +23 -0
  36. data/lib/better_structure_sql/generators/sequence_generator.rb +27 -0
  37. data/lib/better_structure_sql/generators/table_generator.rb +126 -0
  38. data/lib/better_structure_sql/generators/trigger_generator.rb +54 -0
  39. data/lib/better_structure_sql/generators/type_generator.rb +47 -0
  40. data/lib/better_structure_sql/generators/view_generator.rb +27 -0
  41. data/lib/better_structure_sql/introspection/extensions.rb +29 -0
  42. data/lib/better_structure_sql/introspection/foreign_keys.rb +29 -0
  43. data/lib/better_structure_sql/introspection/functions.rb +29 -0
  44. data/lib/better_structure_sql/introspection/indexes.rb +29 -0
  45. data/lib/better_structure_sql/introspection/sequences.rb +29 -0
  46. data/lib/better_structure_sql/introspection/tables.rb +29 -0
  47. data/lib/better_structure_sql/introspection/triggers.rb +29 -0
  48. data/lib/better_structure_sql/introspection/types.rb +37 -0
  49. data/lib/better_structure_sql/introspection/views.rb +41 -0
  50. data/lib/better_structure_sql/introspection.rb +31 -0
  51. data/lib/better_structure_sql/manifest_generator.rb +65 -0
  52. data/lib/better_structure_sql/migration_patch.rb +196 -0
  53. data/lib/better_structure_sql/pg_version.rb +44 -0
  54. data/lib/better_structure_sql/railtie.rb +124 -0
  55. data/lib/better_structure_sql/schema_loader.rb +168 -0
  56. data/lib/better_structure_sql/schema_version.rb +86 -0
  57. data/lib/better_structure_sql/schema_versions.rb +213 -0
  58. data/lib/better_structure_sql/version.rb +5 -0
  59. data/lib/better_structure_sql/zip_generator.rb +81 -0
  60. data/lib/better_structure_sql.rb +81 -0
  61. data/lib/generators/better_structure_sql/install_generator.rb +44 -0
  62. data/lib/generators/better_structure_sql/migration_generator.rb +34 -0
  63. data/lib/generators/better_structure_sql/templates/README +49 -0
  64. data/lib/generators/better_structure_sql/templates/add_metadata_migration.rb.erb +25 -0
  65. data/lib/generators/better_structure_sql/templates/better_structure_sql.rb +46 -0
  66. data/lib/generators/better_structure_sql/templates/migration.rb.erb +26 -0
  67. data/lib/tasks/better_structure_sql.rake +190 -0
  68. metadata +299 -0
@@ -0,0 +1,213 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterStructureSql
4
+ # Schema version storage and retrieval operations
5
+ #
6
+ # Provides class methods for storing, querying, and managing
7
+ # schema versions with automatic retention cleanup.
8
+ module SchemaVersions
9
+ class << self
10
+ # Store current schema from file
11
+ def store_current(connection = ActiveRecord::Base.connection)
12
+ config = BetterStructureSql.configuration
13
+ pg_version = PgVersion.detect(connection)
14
+
15
+ # Determine format and output mode
16
+ format_type = deduce_format_type(config.output_path)
17
+ output_mode = detect_output_mode(config.output_path)
18
+
19
+ # Read content
20
+ content, zip_archive, file_count = read_schema_content(config.output_path, output_mode)
21
+
22
+ return nil unless content
23
+
24
+ store(
25
+ content: content,
26
+ zip_archive: zip_archive,
27
+ format_type: format_type,
28
+ output_mode: output_mode,
29
+ pg_version: pg_version,
30
+ file_count: file_count,
31
+ connection: connection
32
+ )
33
+ end
34
+
35
+ # Store schema version with explicit parameters
36
+ def store(content:, format_type:, pg_version:, **options)
37
+ connection = options.fetch(:connection, ActiveRecord::Base.connection)
38
+ output_mode = options.fetch(:output_mode, 'single_file')
39
+ zip_archive = options[:zip_archive]
40
+ file_count = options[:file_count]
41
+
42
+ ensure_table_exists!(connection)
43
+
44
+ version = SchemaVersion.create!(
45
+ content: content,
46
+ zip_archive: zip_archive,
47
+ format_type: format_type,
48
+ output_mode: output_mode,
49
+ pg_version: pg_version,
50
+ file_count: file_count,
51
+ created_at: Time.current
52
+ )
53
+
54
+ cleanup!(connection)
55
+
56
+ version
57
+ end
58
+
59
+ # Retrieval methods
60
+ def latest
61
+ return nil unless table_exists?
62
+
63
+ SchemaVersion.latest
64
+ end
65
+
66
+ # Returns all stored schema versions
67
+ #
68
+ # @return [Array<SchemaVersion>] All versions ordered by creation date (newest first)
69
+ def all_versions
70
+ return [] unless table_exists?
71
+
72
+ SchemaVersion.order(created_at: :desc).to_a
73
+ end
74
+
75
+ # Finds schema version by ID
76
+ #
77
+ # @param id [Integer] Version ID
78
+ # @return [SchemaVersion, nil] Found version or nil
79
+ def find(id)
80
+ return nil unless table_exists?
81
+
82
+ SchemaVersion.find_by(id: id)
83
+ end
84
+
85
+ # Returns total count of stored versions
86
+ #
87
+ # @return [Integer] Version count
88
+ def count
89
+ return 0 unless table_exists?
90
+
91
+ SchemaVersion.count
92
+ end
93
+
94
+ # Returns versions filtered by format type
95
+ #
96
+ # @param format_type [String] Format type ('sql' or 'rb')
97
+ # @return [Array<SchemaVersion>] Matching versions
98
+ def by_format(format_type)
99
+ return [] unless table_exists?
100
+
101
+ SchemaVersion.by_format(format_type).order(created_at: :desc).to_a
102
+ end
103
+
104
+ # Retention management
105
+ def cleanup!(_connection = ActiveRecord::Base.connection)
106
+ return 0 unless table_exists?
107
+
108
+ config = BetterStructureSql.configuration
109
+ limit = config.schema_versions_limit
110
+
111
+ # Skip cleanup if unlimited (0)
112
+ return 0 if limit.zero?
113
+
114
+ # Delete oldest versions beyond limit
115
+ total_count = SchemaVersion.count
116
+ return 0 if total_count <= limit
117
+
118
+ versions_to_delete = total_count - limit
119
+ oldest_versions = SchemaVersion.oldest_first.limit(versions_to_delete)
120
+
121
+ deleted_count = 0
122
+ oldest_versions.each do |version|
123
+ version.destroy
124
+ deleted_count += 1
125
+ end
126
+
127
+ deleted_count
128
+ end
129
+
130
+ private
131
+
132
+ def read_schema_content(output_path, output_mode)
133
+ case output_mode
134
+ when 'single_file'
135
+ full_path = Rails.root.join(output_path)
136
+ return [nil, nil, nil] unless File.exist?(full_path)
137
+
138
+ content = File.read(full_path)
139
+ [content, nil, nil]
140
+
141
+ when 'multi_file'
142
+ full_path = Rails.root.join(output_path)
143
+ return [nil, nil, nil] unless Dir.exist?(full_path)
144
+
145
+ # Read all files and combine into single content
146
+ content = read_multi_file_content(full_path)
147
+
148
+ # Create ZIP archive from directory
149
+ zip_archive = ZipGenerator.create_from_directory(full_path)
150
+
151
+ # Count files
152
+ file_count = Dir.glob("#{full_path}/**/*.sql").count
153
+
154
+ [content, zip_archive, file_count]
155
+ end
156
+ end
157
+
158
+ def read_multi_file_content(base_path)
159
+ content_parts = []
160
+
161
+ # Read header
162
+ header_path = base_path.join('_header.sql')
163
+ content_parts << File.read(header_path) if File.exist?(header_path)
164
+
165
+ # Read manifest and embed as SQL comment for later extraction
166
+ manifest_path = base_path.join('_manifest.json')
167
+ if File.exist?(manifest_path)
168
+ manifest_json = File.read(manifest_path)
169
+ content_parts << "-- MANIFEST_JSON_START\n-- #{manifest_json.gsub("\n", "\n-- ")}\n-- MANIFEST_JSON_END"
170
+ end
171
+
172
+ # Read numbered directories in order (01_ through 10_)
173
+ # Use pattern that works with Dir.glob
174
+ Dir.glob(File.join(base_path, '*_*')).select { |f| File.directory?(f) }.sort.each do |dir|
175
+ Dir.glob(File.join(dir, '*.sql')).sort.each do |file|
176
+ content_parts << File.read(file)
177
+ end
178
+ end
179
+
180
+ content_parts.join("\n\n")
181
+ end
182
+
183
+ def detect_output_mode(path)
184
+ # No extension → directory → multi_file
185
+ # Has extension (.sql, .rb) → file → single_file
186
+ File.extname(path.to_s).empty? ? 'multi_file' : 'single_file'
187
+ end
188
+
189
+ def deduce_format_type(path)
190
+ # Deduce format from file extension in output_path
191
+ if path.to_s.end_with?('.rb')
192
+ 'rb'
193
+ else
194
+ 'sql' # Default to SQL for .sql or any other extension
195
+ end
196
+ end
197
+
198
+ def ensure_table_exists!(_connection)
199
+ return if table_exists?
200
+
201
+ raise Error, "Schema versions table does not exist. Run migration first:\n " \
202
+ "rails generate better_structure_sql:migration\n " \
203
+ 'rails db:migrate'
204
+ end
205
+
206
+ def table_exists?
207
+ ActiveRecord::Base.connection.table_exists?('better_structure_sql_schema_versions')
208
+ rescue ActiveRecord::NoDatabaseError
209
+ false
210
+ end
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterStructureSql
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'zip'
4
+ require 'stringio'
5
+
6
+ module BetterStructureSql
7
+ # Handles creating and extracting ZIP archives for multi-file schema dumps
8
+ class ZipGenerator
9
+ MAX_FILES_IN_ZIP = 300_000
10
+ MAX_UNCOMPRESSED_SIZE = 800.megabytes
11
+
12
+ class ZipError < StandardError; end
13
+
14
+ # Create ZIP from existing directory
15
+ # @param dir_path [String, Pathname] The directory path
16
+ # @return [String] ZIP file binary data
17
+ def self.create_from_directory(dir_path)
18
+ buffer = Zip::OutputStream.write_buffer do |zip|
19
+ Dir.glob("#{dir_path}/**/*").sort.each do |file_path|
20
+ next if File.directory?(file_path)
21
+
22
+ relative_path = file_path.sub("#{dir_path}/", '')
23
+ zip.put_next_entry(relative_path)
24
+ zip.write File.read(file_path)
25
+ end
26
+ end
27
+
28
+ buffer.string
29
+ end
30
+
31
+ # Create ZIP from file map (path => content hash)
32
+ # @param file_map [Hash<String, String>] Hash of relative paths to content
33
+ # @return [String] ZIP file binary data
34
+ def self.create_from_file_map(file_map)
35
+ buffer = Zip::OutputStream.write_buffer do |zip|
36
+ file_map.sort.each do |path, content|
37
+ zip.put_next_entry(path)
38
+ zip.write content
39
+ end
40
+ end
41
+
42
+ buffer.string
43
+ end
44
+
45
+ # Extract ZIP to directory
46
+ # @param zip_binary [String] ZIP file binary data
47
+ # @param target_dir [String, Pathname] Target directory path
48
+ def self.extract_to_directory(zip_binary, target_dir)
49
+ FileUtils.mkdir_p(target_dir)
50
+
51
+ Zip::File.open_buffer(StringIO.new(zip_binary)) do |zip_file|
52
+ zip_file.each do |entry|
53
+ # Security: rubyzip already prevents path traversal, but verify anyway
54
+ next if entry.name.include?('..')
55
+
56
+ path = File.join(target_dir, entry.name)
57
+ FileUtils.mkdir_p(File.dirname(path))
58
+ entry.extract(path) unless File.exist?(path)
59
+ end
60
+ end
61
+ end
62
+
63
+ # Validate ZIP safety (prevent ZIP bombs)
64
+ # @param zip_binary [String] ZIP file binary data
65
+ # @raise [ZipError] if ZIP exceeds safety limits
66
+ def self.validate_zip!(zip_binary)
67
+ file_count = 0
68
+ total_size = 0
69
+
70
+ Zip::File.open_buffer(StringIO.new(zip_binary)) do |zip_file|
71
+ zip_file.each do |entry|
72
+ file_count += 1
73
+ total_size += entry.size
74
+
75
+ raise ZipError, "Too many files in ZIP (max #{MAX_FILES_IN_ZIP})" if file_count > MAX_FILES_IN_ZIP
76
+ raise ZipError, "ZIP content too large (max #{MAX_UNCOMPRESSED_SIZE} bytes)" if total_size > MAX_UNCOMPRESSED_SIZE
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record'
4
+ require_relative 'better_structure_sql/version'
5
+ require_relative 'better_structure_sql/adapters/base_adapter'
6
+ require_relative 'better_structure_sql/adapters/postgresql_config'
7
+ require_relative 'better_structure_sql/adapters/registry'
8
+ require_relative 'better_structure_sql/configuration'
9
+ require_relative 'better_structure_sql/dependency_resolver'
10
+ require_relative 'better_structure_sql/introspection'
11
+ require_relative 'better_structure_sql/formatter'
12
+ require_relative 'better_structure_sql/generators/base'
13
+ require_relative 'better_structure_sql/generators/extension_generator'
14
+ require_relative 'better_structure_sql/generators/type_generator'
15
+ require_relative 'better_structure_sql/generators/sequence_generator'
16
+ require_relative 'better_structure_sql/generators/table_generator'
17
+ require_relative 'better_structure_sql/generators/index_generator'
18
+ require_relative 'better_structure_sql/generators/foreign_key_generator'
19
+ require_relative 'better_structure_sql/generators/view_generator'
20
+ require_relative 'better_structure_sql/generators/materialized_view_generator'
21
+ require_relative 'better_structure_sql/generators/function_generator'
22
+ require_relative 'better_structure_sql/generators/trigger_generator'
23
+ require_relative 'better_structure_sql/generators/domain_generator'
24
+ require_relative 'better_structure_sql/database_version'
25
+ require_relative 'better_structure_sql/pg_version'
26
+ require_relative 'better_structure_sql/schema_version'
27
+ require_relative 'better_structure_sql/schema_versions'
28
+ require_relative 'better_structure_sql/file_writer'
29
+ require_relative 'better_structure_sql/manifest_generator'
30
+ require_relative 'better_structure_sql/zip_generator'
31
+ require_relative 'better_structure_sql/schema_loader'
32
+ require_relative 'better_structure_sql/migration_patch'
33
+ require_relative 'better_structure_sql/dumper'
34
+ require_relative 'better_structure_sql/railtie' if defined?(Rails::Railtie)
35
+ require_relative 'better_structure_sql/engine' if defined?(Rails::Engine)
36
+
37
+ # BetterStructureSql - Clean PostgreSQL schema dumps for Rails applications
38
+ #
39
+ # Replaces noisy structure.sql files with deterministic, maintainable output
40
+ # using pure Ruby database introspection. Supports PostgreSQL, MySQL, and SQLite.
41
+ module BetterStructureSql
42
+ # Base error class for all BetterStructureSql errors
43
+ class Error < StandardError; end
44
+
45
+ class << self
46
+ attr_writer :configuration
47
+
48
+ # Returns the current configuration instance
49
+ #
50
+ # @return [Configuration] The configuration object
51
+ def configuration
52
+ @configuration ||= Configuration.new
53
+ end
54
+
55
+ # Configures BetterStructureSql with a block
56
+ #
57
+ # @yield [Configuration] The configuration object
58
+ # @example
59
+ # BetterStructureSql.configure do |config|
60
+ # config.output_path = 'db/structure'
61
+ # config.include_extensions = true
62
+ # end
63
+ def configure
64
+ yield(configuration)
65
+ end
66
+
67
+ # Resets configuration to default values
68
+ #
69
+ # @return [Configuration] A new configuration instance
70
+ def reset_configuration
71
+ @configuration = Configuration.new
72
+ end
73
+
74
+ # Check if BetterStructureSql has been configured
75
+ #
76
+ # @return [Boolean] True if configuration has been set
77
+ def configured?
78
+ !@configuration.nil?
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators/base'
4
+
5
+ module BetterStructureSql
6
+ module Generators
7
+ # Rails generator for installing BetterStructureSql
8
+ #
9
+ # Creates initializer and optionally generates migration for schema_versions table.
10
+ class InstallGenerator < Rails::Generators::Base
11
+ source_root File.expand_path('templates', __dir__)
12
+
13
+ desc 'Creates BetterStructureSql initializer and optionally generates migration'
14
+
15
+ class_option :skip_migration,
16
+ type: :boolean,
17
+ default: false,
18
+ desc: 'Skip migration generation for schema versions table'
19
+
20
+ # Copies initializer template to config/initializers
21
+ #
22
+ # @return [void]
23
+ def copy_initializer
24
+ template 'better_structure_sql.rb', 'config/initializers/better_structure_sql.rb'
25
+ end
26
+
27
+ # Creates migration for schema_versions table
28
+ #
29
+ # @return [void]
30
+ def create_migration
31
+ return if options[:skip_migration]
32
+
33
+ generate 'better_structure_sql:migration'
34
+ end
35
+
36
+ # Displays README after installation
37
+ #
38
+ # @return [void]
39
+ def show_readme
40
+ readme 'README' if behavior == :invoke
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'rails/generators/active_record'
5
+
6
+ module BetterStructureSql
7
+ module Generators
8
+ # Rails generator for creating schema_versions table migration
9
+ class MigrationGenerator < Rails::Generators::Base
10
+ include ActiveRecord::Generators::Migration
11
+
12
+ source_root File.expand_path('templates', __dir__)
13
+
14
+ desc 'Creates migration for BetterStructureSql schema versions table'
15
+
16
+ # Creates migration file for schema_versions table
17
+ #
18
+ # @return [void]
19
+ def create_migration_file
20
+ migration_template(
21
+ 'migration.rb.erb',
22
+ 'db/migrate/create_better_structure_sql_schema_versions.rb',
23
+ migration_version: migration_version
24
+ )
25
+ end
26
+
27
+ private
28
+
29
+ def migration_version
30
+ "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,49 @@
1
+ ===============================================================================
2
+
3
+ BetterStructureSql has been installed!
4
+
5
+ Configuration file created at:
6
+ config/initializers/better_structure_sql.rb
7
+
8
+ To generate a clean schema dump, run:
9
+ rails db:schema:dump_better
10
+
11
+ To replace Rails' default schema dump task, set in the initializer:
12
+ config.replace_default_dump = true
13
+
14
+ Then you can use the standard command:
15
+ rails db:schema:dump
16
+
17
+ Phase 1 features available:
18
+ ✓ Tables with columns, primary keys, and constraints
19
+ ✓ Indexes (including unique and partial)
20
+ ✓ Foreign keys with CASCADE/RESTRICT/SET NULL
21
+ ✓ PostgreSQL extensions
22
+ ✓ Clean, deterministic output
23
+
24
+ Phase 2 features available:
25
+ ✓ Schema version storage
26
+ ✓ Version retention management
27
+ ✓ PostgreSQL version tracking
28
+
29
+ To enable schema versioning:
30
+ 1. Enable in config/initializers/better_structure_sql.rb:
31
+ config.enable_schema_versions = true
32
+ 2. Run the migration (if not already generated):
33
+ rails db:migrate
34
+ 3. Store current schema:
35
+ rails db:schema:store
36
+ 4. List versions:
37
+ rails db:schema:versions
38
+ 5. Cleanup old versions:
39
+ rails db:schema:cleanup
40
+
41
+ Coming in Phase 3:
42
+ - Views and materialized views
43
+ - Functions and triggers
44
+ - Advanced PostgreSQL features
45
+
46
+ For more information, visit:
47
+ https://github.com/example/better_structure_sql
48
+
49
+ ===============================================================================
@@ -0,0 +1,25 @@
1
+ class AddMetadataToBetterStructureSqlSchemaVersions < ActiveRecord::Migration<%= migration_version %>
2
+ def up
3
+ # Add new columns
4
+ add_column :better_structure_sql_schema_versions, :content_size, :bigint
5
+ add_column :better_structure_sql_schema_versions, :line_count, :integer
6
+
7
+ # Backfill existing records
8
+ BetterStructureSql::SchemaVersion.reset_column_information
9
+ BetterStructureSql::SchemaVersion.find_each do |version|
10
+ version.update_columns(
11
+ content_size: version.content.bytesize,
12
+ line_count: version.content.lines.count
13
+ )
14
+ end
15
+
16
+ # Make columns non-nullable
17
+ change_column_null :better_structure_sql_schema_versions, :content_size, false
18
+ change_column_null :better_structure_sql_schema_versions, :line_count, false
19
+ end
20
+
21
+ def down
22
+ remove_column :better_structure_sql_schema_versions, :line_count
23
+ remove_column :better_structure_sql_schema_versions, :content_size
24
+ end
25
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ BetterStructureSql.configure do |config|
4
+ # Output path for structure dump
5
+ # Single file: 'db/structure.sql'
6
+ # Multi-file: 'db/schema' (directory) - RECOMMENDED for large projects
7
+ # Benefits: better git diffs, easier navigation, AI-friendly organization
8
+ # NOTE: BetterStructureSql only supports SQL format dumps (structure.sql).
9
+ # Using 'db/schema.rb' will skip replacement of dump/load tasks (can still store versions).
10
+ config.output_path = Rails.root.join('db/structure.sql')
11
+
12
+ # Schema search path (PostgreSQL only)
13
+ config.search_path = 'public'
14
+
15
+ # Feature toggles - what to include in dumps
16
+ # Features auto-skip if not supported by your database
17
+ config.include_extensions = true # PostgreSQL only
18
+ config.include_custom_types = true # PostgreSQL (ENUM, composite), MySQL (ENUM/SET)
19
+ config.include_domains = true # PostgreSQL only
20
+ config.include_sequences = true # PostgreSQL only
21
+ config.include_functions = true # PostgreSQL, MySQL (stored procedures)
22
+ config.include_triggers = true # All databases
23
+ config.include_views = true # All databases
24
+ config.include_materialized_views = true # PostgreSQL only
25
+
26
+ # Output formatting
27
+ config.add_section_spacing = true # Add blank lines between sections
28
+ config.sort_tables = false # Sort tables alphabetically (or use dependency order)
29
+
30
+ # Multi-file output settings (only used when output_path is a directory)
31
+ config.max_lines_per_file = 500 # Target lines per file (soft limit)
32
+ config.overflow_threshold = 1.1 # Allow files to be up to 10% larger to avoid tiny files
33
+ config.generate_manifest = true # Generate _manifest.json with statistics
34
+
35
+ # Schema versioning (stores schema history in database for rollback/comparison)
36
+ config.enable_schema_versions = true # Store versions in database
37
+ config.schema_versions_limit = 10 # Keep last 10 versions (0 = unlimited)
38
+
39
+ # Replace default Rails schema dump/load tasks (opt-in)
40
+ # When true, db:schema:dump and db:schema:load will use BetterStructureSql automatically
41
+ # This also automatically sets config.active_record.schema_format = :sql
42
+ # When false (default), use explicit tasks: db:schema:dump_better and db:schema:load_better
43
+ # NOTE: Only works with SQL format. Silently ignored if output_path ends with '.rb'
44
+ config.replace_default_dump = false
45
+ config.replace_default_load = false
46
+ end
@@ -0,0 +1,26 @@
1
+ class CreateBetterStructureSqlSchemaVersions < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table :better_structure_sql_schema_versions do |t|
4
+ t.text :content, null: false
5
+ t.binary :zip_archive, null: true
6
+ t.string :pg_version, null: false
7
+ t.string :format_type, null: false
8
+ t.string :output_mode, null: false
9
+ t.bigint :content_size, null: false
10
+ t.integer :line_count, null: false
11
+ t.integer :file_count, null: true
12
+ t.timestamp :created_at, null: false
13
+ end
14
+
15
+ add_index :better_structure_sql_schema_versions, :created_at, order: { created_at: :desc }
16
+ add_index :better_structure_sql_schema_versions, :output_mode
17
+
18
+ add_check_constraint :better_structure_sql_schema_versions,
19
+ "format_type IN ('sql', 'rb')",
20
+ name: 'format_type_check'
21
+
22
+ add_check_constraint :better_structure_sql_schema_versions,
23
+ "output_mode IN ('single_file', 'multi_file')",
24
+ name: 'output_mode_check'
25
+ end
26
+ end