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,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