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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +41 -0
- data/LICENSE +21 -0
- data/README.md +557 -0
- data/app/controllers/better_structure_sql/application_controller.rb +61 -0
- data/app/controllers/better_structure_sql/schema_versions_controller.rb +243 -0
- data/app/helpers/better_structure_sql/schema_versions_helper.rb +46 -0
- data/app/views/better_structure_sql/schema_versions/index.html.erb +110 -0
- data/app/views/better_structure_sql/schema_versions/show.html.erb +186 -0
- data/app/views/layouts/better_structure_sql/application.html.erb +105 -0
- data/config/database.yml +3 -0
- data/config/routes.rb +12 -0
- data/lib/better_structure_sql/adapters/base_adapter.rb +234 -0
- data/lib/better_structure_sql/adapters/mysql_adapter.rb +476 -0
- data/lib/better_structure_sql/adapters/mysql_config.rb +32 -0
- data/lib/better_structure_sql/adapters/postgresql_adapter.rb +646 -0
- data/lib/better_structure_sql/adapters/postgresql_config.rb +25 -0
- data/lib/better_structure_sql/adapters/registry.rb +115 -0
- data/lib/better_structure_sql/adapters/sqlite_adapter.rb +644 -0
- data/lib/better_structure_sql/adapters/sqlite_config.rb +26 -0
- data/lib/better_structure_sql/configuration.rb +129 -0
- data/lib/better_structure_sql/database_version.rb +46 -0
- data/lib/better_structure_sql/dependency_resolver.rb +63 -0
- data/lib/better_structure_sql/dumper.rb +544 -0
- data/lib/better_structure_sql/engine.rb +28 -0
- data/lib/better_structure_sql/file_writer.rb +180 -0
- data/lib/better_structure_sql/formatter.rb +70 -0
- data/lib/better_structure_sql/generators/base.rb +33 -0
- data/lib/better_structure_sql/generators/domain_generator.rb +22 -0
- data/lib/better_structure_sql/generators/extension_generator.rb +23 -0
- data/lib/better_structure_sql/generators/foreign_key_generator.rb +43 -0
- data/lib/better_structure_sql/generators/function_generator.rb +33 -0
- data/lib/better_structure_sql/generators/index_generator.rb +50 -0
- data/lib/better_structure_sql/generators/materialized_view_generator.rb +31 -0
- data/lib/better_structure_sql/generators/pragma_generator.rb +23 -0
- data/lib/better_structure_sql/generators/sequence_generator.rb +27 -0
- data/lib/better_structure_sql/generators/table_generator.rb +126 -0
- data/lib/better_structure_sql/generators/trigger_generator.rb +54 -0
- data/lib/better_structure_sql/generators/type_generator.rb +47 -0
- data/lib/better_structure_sql/generators/view_generator.rb +27 -0
- data/lib/better_structure_sql/introspection/extensions.rb +29 -0
- data/lib/better_structure_sql/introspection/foreign_keys.rb +29 -0
- data/lib/better_structure_sql/introspection/functions.rb +29 -0
- data/lib/better_structure_sql/introspection/indexes.rb +29 -0
- data/lib/better_structure_sql/introspection/sequences.rb +29 -0
- data/lib/better_structure_sql/introspection/tables.rb +29 -0
- data/lib/better_structure_sql/introspection/triggers.rb +29 -0
- data/lib/better_structure_sql/introspection/types.rb +37 -0
- data/lib/better_structure_sql/introspection/views.rb +41 -0
- data/lib/better_structure_sql/introspection.rb +31 -0
- data/lib/better_structure_sql/manifest_generator.rb +65 -0
- data/lib/better_structure_sql/migration_patch.rb +196 -0
- data/lib/better_structure_sql/pg_version.rb +44 -0
- data/lib/better_structure_sql/railtie.rb +124 -0
- data/lib/better_structure_sql/schema_loader.rb +168 -0
- data/lib/better_structure_sql/schema_version.rb +86 -0
- data/lib/better_structure_sql/schema_versions.rb +213 -0
- data/lib/better_structure_sql/version.rb +5 -0
- data/lib/better_structure_sql/zip_generator.rb +81 -0
- data/lib/better_structure_sql.rb +81 -0
- data/lib/generators/better_structure_sql/install_generator.rb +44 -0
- data/lib/generators/better_structure_sql/migration_generator.rb +34 -0
- data/lib/generators/better_structure_sql/templates/README +49 -0
- data/lib/generators/better_structure_sql/templates/add_metadata_migration.rb.erb +25 -0
- data/lib/generators/better_structure_sql/templates/better_structure_sql.rb +46 -0
- data/lib/generators/better_structure_sql/templates/migration.rb.erb +26 -0
- data/lib/tasks/better_structure_sql.rake +190 -0
- metadata +299 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterStructureSql
|
|
4
|
+
# Configuration management for BetterStructureSql
|
|
5
|
+
#
|
|
6
|
+
# Provides centralized settings for dump behavior, feature toggles,
|
|
7
|
+
# file output options, and schema versioning settings.
|
|
8
|
+
class Configuration
|
|
9
|
+
attr_accessor :search_path,
|
|
10
|
+
:replace_default_dump,
|
|
11
|
+
:replace_default_load,
|
|
12
|
+
:include_extensions,
|
|
13
|
+
:include_functions,
|
|
14
|
+
:include_triggers,
|
|
15
|
+
:include_views,
|
|
16
|
+
:include_materialized_views,
|
|
17
|
+
:include_rules,
|
|
18
|
+
:include_comments,
|
|
19
|
+
:include_domains,
|
|
20
|
+
:include_sequences,
|
|
21
|
+
:include_custom_types,
|
|
22
|
+
:enable_schema_versions,
|
|
23
|
+
:schema_versions_limit,
|
|
24
|
+
:schemas,
|
|
25
|
+
:indent_size,
|
|
26
|
+
:add_section_spacing,
|
|
27
|
+
:sort_tables,
|
|
28
|
+
:max_lines_per_file,
|
|
29
|
+
:overflow_threshold,
|
|
30
|
+
:generate_manifest,
|
|
31
|
+
:adapter
|
|
32
|
+
|
|
33
|
+
attr_reader :output_path, :postgresql
|
|
34
|
+
|
|
35
|
+
# Sets the output path for schema dump
|
|
36
|
+
#
|
|
37
|
+
# @param value [String, Pathname] Path to output file or directory
|
|
38
|
+
# @return [String] The configured path as a string
|
|
39
|
+
def output_path=(value)
|
|
40
|
+
@output_path = value.to_s
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def initialize
|
|
44
|
+
@output_path = 'db/structure.sql'
|
|
45
|
+
@search_path = '"$user", public'
|
|
46
|
+
@replace_default_dump = false
|
|
47
|
+
@replace_default_load = false
|
|
48
|
+
@include_extensions = true
|
|
49
|
+
@include_functions = true
|
|
50
|
+
@include_triggers = true
|
|
51
|
+
@include_views = true
|
|
52
|
+
@include_materialized_views = true
|
|
53
|
+
@include_rules = false
|
|
54
|
+
@include_comments = false
|
|
55
|
+
@include_domains = true
|
|
56
|
+
@include_sequences = true
|
|
57
|
+
@include_custom_types = true
|
|
58
|
+
@enable_schema_versions = false
|
|
59
|
+
@schema_versions_limit = 10
|
|
60
|
+
@schemas = ['public']
|
|
61
|
+
@indent_size = 2
|
|
62
|
+
@add_section_spacing = true
|
|
63
|
+
@sort_tables = true
|
|
64
|
+
@max_lines_per_file = 500
|
|
65
|
+
@overflow_threshold = 1.1
|
|
66
|
+
@generate_manifest = true
|
|
67
|
+
@adapter = :auto
|
|
68
|
+
@postgresql = Adapters::PostgresqlConfig.new
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Validates configuration settings
|
|
72
|
+
#
|
|
73
|
+
# @raise [Error] If any configuration setting is invalid
|
|
74
|
+
# @return [void]
|
|
75
|
+
def validate!
|
|
76
|
+
validate_output_path!
|
|
77
|
+
validate_schema_versions_limit!
|
|
78
|
+
validate_indent_size!
|
|
79
|
+
validate_schemas!
|
|
80
|
+
validate_max_lines_per_file!
|
|
81
|
+
validate_overflow_threshold!
|
|
82
|
+
validate_adapter!
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def validate_output_path!
|
|
88
|
+
raise Error, 'output_path cannot be blank' if output_path.nil? || output_path.strip.empty?
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def validate_schema_versions_limit!
|
|
92
|
+
return if schema_versions_limit.is_a?(Integer) && schema_versions_limit >= 0
|
|
93
|
+
|
|
94
|
+
raise Error, 'schema_versions_limit must be a non-negative integer'
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def validate_indent_size!
|
|
98
|
+
return if indent_size.is_a?(Integer) && indent_size.positive?
|
|
99
|
+
|
|
100
|
+
raise Error, 'indent_size must be a positive integer'
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def validate_schemas!
|
|
104
|
+
return if schemas.is_a?(Array) && schemas.any?
|
|
105
|
+
|
|
106
|
+
raise Error, 'schemas must be a non-empty array'
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def validate_max_lines_per_file!
|
|
110
|
+
return if max_lines_per_file.is_a?(Integer) && max_lines_per_file.positive?
|
|
111
|
+
|
|
112
|
+
raise Error, 'max_lines_per_file must be a positive integer'
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def validate_overflow_threshold!
|
|
116
|
+
return if overflow_threshold.is_a?(Numeric) && overflow_threshold >= 1.0
|
|
117
|
+
|
|
118
|
+
raise Error, 'overflow_threshold must be >= 1.0'
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def validate_adapter!
|
|
122
|
+
valid_adapters = %i[auto postgresql mysql sqlite]
|
|
123
|
+
|
|
124
|
+
return if valid_adapters.include?(adapter)
|
|
125
|
+
|
|
126
|
+
raise Error, "Invalid adapter: #{adapter}. Valid options: #{valid_adapters.join(', ')}"
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterStructureSql
|
|
4
|
+
# Database version detection abstraction.
|
|
5
|
+
# Delegates to the appropriate adapter for version detection.
|
|
6
|
+
module DatabaseVersion
|
|
7
|
+
class << self
|
|
8
|
+
# Detects database version using the appropriate adapter
|
|
9
|
+
#
|
|
10
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
11
|
+
# @return [String] Database version string
|
|
12
|
+
def detect(connection = ActiveRecord::Base.connection)
|
|
13
|
+
adapter = Adapters::Registry.adapter_for(
|
|
14
|
+
connection,
|
|
15
|
+
adapter_override: BetterStructureSql.configuration.adapter
|
|
16
|
+
)
|
|
17
|
+
adapter.database_version
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# For backward compatibility with PgVersion
|
|
21
|
+
def parse_version(version_string)
|
|
22
|
+
# Parse version string directly (PostgreSQL format)
|
|
23
|
+
# Example: "PostgreSQL 15.1 on x86_64" → "15.1"
|
|
24
|
+
match = version_string.match(/(\d+\.\d+(\.\d+)?)/)
|
|
25
|
+
match ? match[1] : 'unknown'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Extracts major version number from version string
|
|
29
|
+
#
|
|
30
|
+
# @param version_string [String] Version string (e.g., "15.1.0")
|
|
31
|
+
# @return [Integer] Major version number
|
|
32
|
+
def major_version(version_string)
|
|
33
|
+
version_string.split('.').first.to_i
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Extracts minor version number from version string
|
|
37
|
+
#
|
|
38
|
+
# @param version_string [String] Version string (e.g., "15.1.0")
|
|
39
|
+
# @return [Integer] Minor version number
|
|
40
|
+
def minor_version(version_string)
|
|
41
|
+
parts = version_string.split('.')
|
|
42
|
+
parts.length > 1 ? parts[1].to_i : 0
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterStructureSql
|
|
4
|
+
# Resolves dependencies between database objects for correct ordering
|
|
5
|
+
#
|
|
6
|
+
# Uses topological sorting to ensure objects are created in dependency order,
|
|
7
|
+
# handling circular dependencies gracefully.
|
|
8
|
+
class DependencyResolver
|
|
9
|
+
attr_reader :objects, :dependencies
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@objects = []
|
|
13
|
+
@dependencies = Hash.new { |h, k| h[k] = [] }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Adds an object and its dependencies to the resolver
|
|
17
|
+
#
|
|
18
|
+
# @param name [String] Object name
|
|
19
|
+
# @param type [Symbol] Object type (e.g., :table, :view, :function)
|
|
20
|
+
# @param depends_on [Array<String>] Names of objects this depends on
|
|
21
|
+
# @return [void]
|
|
22
|
+
def add_object(name, type, depends_on: [])
|
|
23
|
+
@objects << { name: name, type: type }
|
|
24
|
+
@dependencies[name] = Array(depends_on)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Resolves dependencies and returns objects in correct order
|
|
28
|
+
#
|
|
29
|
+
# @return [Array<String>] Object names in dependency order
|
|
30
|
+
def resolve
|
|
31
|
+
sorted = []
|
|
32
|
+
visited = Set.new
|
|
33
|
+
temp_mark = Set.new
|
|
34
|
+
|
|
35
|
+
@objects.each do |obj|
|
|
36
|
+
visit(obj[:name], visited, temp_mark, sorted) unless visited.include?(obj[:name])
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
sorted
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def visit(name, visited, temp_mark, sorted)
|
|
45
|
+
if temp_mark.include?(name)
|
|
46
|
+
# Circular dependency detected - return to allow best-effort ordering
|
|
47
|
+
return
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
return if visited.include?(name)
|
|
51
|
+
|
|
52
|
+
temp_mark.add(name)
|
|
53
|
+
|
|
54
|
+
@dependencies[name].each do |dep|
|
|
55
|
+
visit(dep, visited, temp_mark, sorted) if @objects.any? { |o| o[:name] == dep }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
temp_mark.delete(name)
|
|
59
|
+
visited.add(name)
|
|
60
|
+
sorted << name
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|