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,105 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Schema Versions - BetterStructureSql</title>
|
|
7
|
+
|
|
8
|
+
<!-- Bootstrap 5.3 CSS from CDN -->
|
|
9
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
|
|
10
|
+
|
|
11
|
+
<!-- Bootstrap Icons from CDN -->
|
|
12
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.2/font/bootstrap-icons.min.css">
|
|
13
|
+
|
|
14
|
+
<style>
|
|
15
|
+
.code-block {
|
|
16
|
+
background: #f8f9fa;
|
|
17
|
+
border: 1px solid #dee2e6;
|
|
18
|
+
border-radius: 0.375rem;
|
|
19
|
+
padding: 1rem;
|
|
20
|
+
overflow-x: auto;
|
|
21
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
|
|
22
|
+
font-size: 0.875rem;
|
|
23
|
+
line-height: 1.5;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.metadata-card {
|
|
27
|
+
background: #f8f9fa;
|
|
28
|
+
border-left: 4px solid #0d6efd;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.navbar-brand {
|
|
32
|
+
font-weight: 600;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.empty-state {
|
|
36
|
+
padding: 4rem 2rem;
|
|
37
|
+
text-align: center;
|
|
38
|
+
color: #6c757d;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.badge-format {
|
|
42
|
+
font-weight: 500;
|
|
43
|
+
}
|
|
44
|
+
</style>
|
|
45
|
+
</head>
|
|
46
|
+
<body>
|
|
47
|
+
<!-- Navigation -->
|
|
48
|
+
<nav class="navbar navbar-expand-lg navbar-dark bg-primary mb-4">
|
|
49
|
+
<div class="container-fluid">
|
|
50
|
+
<a class="navbar-brand" href="<%= better_structure_sql.schema_versions_path %>">
|
|
51
|
+
<i class="bi bi-database"></i>
|
|
52
|
+
BetterStructureSql
|
|
53
|
+
</a>
|
|
54
|
+
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
|
55
|
+
<span class="navbar-toggler-icon"></span>
|
|
56
|
+
</button>
|
|
57
|
+
<div class="collapse navbar-collapse" id="navbarNav">
|
|
58
|
+
<ul class="navbar-nav ms-auto">
|
|
59
|
+
<li class="nav-item">
|
|
60
|
+
<a class="nav-link" href="<%= better_structure_sql.schema_versions_path %>">
|
|
61
|
+
<i class="bi bi-list-ul"></i>
|
|
62
|
+
All Versions
|
|
63
|
+
</a>
|
|
64
|
+
</li>
|
|
65
|
+
</ul>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</nav>
|
|
69
|
+
|
|
70
|
+
<!-- Flash Messages -->
|
|
71
|
+
<div class="container-fluid">
|
|
72
|
+
<% if flash[:notice] %>
|
|
73
|
+
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
|
74
|
+
<%= flash[:notice] %>
|
|
75
|
+
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
76
|
+
</div>
|
|
77
|
+
<% end %>
|
|
78
|
+
|
|
79
|
+
<% if flash[:alert] %>
|
|
80
|
+
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
|
81
|
+
<%= flash[:alert] %>
|
|
82
|
+
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
83
|
+
</div>
|
|
84
|
+
<% end %>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<!-- Main Content -->
|
|
88
|
+
<main class="container-fluid">
|
|
89
|
+
<%= yield %>
|
|
90
|
+
</main>
|
|
91
|
+
|
|
92
|
+
<!-- Footer -->
|
|
93
|
+
<footer class="mt-5 py-4 bg-light border-top">
|
|
94
|
+
<div class="container-fluid text-center text-muted">
|
|
95
|
+
<small>
|
|
96
|
+
BetterStructureSql Engine ·
|
|
97
|
+
Clean PostgreSQL schema dumps for Rails
|
|
98
|
+
</small>
|
|
99
|
+
</div>
|
|
100
|
+
</footer>
|
|
101
|
+
|
|
102
|
+
<!-- Bootstrap JS Bundle from CDN -->
|
|
103
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
|
|
104
|
+
</body>
|
|
105
|
+
</html>
|
data/config/database.yml
ADDED
data/config/routes.rb
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterStructureSql
|
|
4
|
+
module Adapters
|
|
5
|
+
# Base adapter class defining the interface contract for database-specific adapters.
|
|
6
|
+
#
|
|
7
|
+
# All concrete adapters must inherit from this class and implement the abstract methods.
|
|
8
|
+
# This class provides the foundation for database introspection and SQL generation across
|
|
9
|
+
# different database systems (PostgreSQL, MySQL, SQLite).
|
|
10
|
+
#
|
|
11
|
+
# @abstract Subclasses must implement all abstract methods
|
|
12
|
+
class BaseAdapter
|
|
13
|
+
# @return [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
14
|
+
attr_reader :connection
|
|
15
|
+
|
|
16
|
+
# Initialize a new adapter instance
|
|
17
|
+
#
|
|
18
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
19
|
+
def initialize(connection)
|
|
20
|
+
@connection = connection
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Abstract introspection methods - must be implemented by subclasses
|
|
24
|
+
|
|
25
|
+
# Fetch database extensions
|
|
26
|
+
#
|
|
27
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
28
|
+
# @return [Array<Hash>] Array of extension hashes with :name, :version, :schema
|
|
29
|
+
# @raise [NotImplementedError] If not implemented by subclass
|
|
30
|
+
def fetch_extensions(connection)
|
|
31
|
+
raise NotImplementedError, "#{self.class} must implement #fetch_extensions"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Fetch custom types (enums, composite types, domains)
|
|
35
|
+
#
|
|
36
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
37
|
+
# @return [Array<Hash>] Array of type hashes with :name, :type, :schema, and type-specific attributes
|
|
38
|
+
# @raise [NotImplementedError] If not implemented by subclass
|
|
39
|
+
def fetch_custom_types(connection)
|
|
40
|
+
raise NotImplementedError, "#{self.class} must implement #fetch_custom_types"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Fetch tables with columns and constraints
|
|
44
|
+
#
|
|
45
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
46
|
+
# @return [Array<Hash>] Array of table hashes with :name, :schema, :columns, :primary_key, :constraints
|
|
47
|
+
# @raise [NotImplementedError] If not implemented by subclass
|
|
48
|
+
def fetch_tables(connection)
|
|
49
|
+
raise NotImplementedError, "#{self.class} must implement #fetch_tables"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Fetch indexes
|
|
53
|
+
#
|
|
54
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
55
|
+
# @return [Array<Hash>] Array of index hashes with :name, :table, :columns, :unique, :type
|
|
56
|
+
# @raise [NotImplementedError] If not implemented by subclass
|
|
57
|
+
def fetch_indexes(connection)
|
|
58
|
+
raise NotImplementedError, "#{self.class} must implement #fetch_indexes"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Fetch foreign keys
|
|
62
|
+
#
|
|
63
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
64
|
+
# @return [Array<Hash>] Array of foreign key hashes with :table, :name, :column, :foreign_table, :foreign_column, :on_update, :on_delete
|
|
65
|
+
# @raise [NotImplementedError] If not implemented by subclass
|
|
66
|
+
def fetch_foreign_keys(connection)
|
|
67
|
+
raise NotImplementedError, "#{self.class} must implement #fetch_foreign_keys"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Fetch views
|
|
71
|
+
#
|
|
72
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
73
|
+
# @return [Array<Hash>] Array of view hashes with :schema, :name, :definition
|
|
74
|
+
# @raise [NotImplementedError] If not implemented by subclass
|
|
75
|
+
def fetch_views(connection)
|
|
76
|
+
raise NotImplementedError, "#{self.class} must implement #fetch_views"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Fetch materialized views
|
|
80
|
+
#
|
|
81
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
82
|
+
# @return [Array<Hash>] Array of materialized view hashes with :schema, :name, :definition, :indexes
|
|
83
|
+
# @raise [NotImplementedError] If not implemented by subclass
|
|
84
|
+
def fetch_materialized_views(connection)
|
|
85
|
+
raise NotImplementedError, "#{self.class} must implement #fetch_materialized_views"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Fetch functions
|
|
89
|
+
#
|
|
90
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
91
|
+
# @return [Array<Hash>] Array of function hashes with :schema, :name, :definition
|
|
92
|
+
# @raise [NotImplementedError] If not implemented by subclass
|
|
93
|
+
def fetch_functions(connection)
|
|
94
|
+
raise NotImplementedError, "#{self.class} must implement #fetch_functions"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Fetch sequences
|
|
98
|
+
#
|
|
99
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
100
|
+
# @return [Array<Hash>] Array of sequence hashes with :name, :schema, :start_value, :increment
|
|
101
|
+
# @raise [NotImplementedError] If not implemented by subclass
|
|
102
|
+
def fetch_sequences(connection)
|
|
103
|
+
raise NotImplementedError, "#{self.class} must implement #fetch_sequences"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Fetch triggers
|
|
107
|
+
#
|
|
108
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
109
|
+
# @return [Array<Hash>] Array of trigger hashes with :schema, :name, :table_name, :definition
|
|
110
|
+
# @raise [NotImplementedError] If not implemented by subclass
|
|
111
|
+
def fetch_triggers(connection)
|
|
112
|
+
raise NotImplementedError, "#{self.class} must implement #fetch_triggers"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Capability methods - indicate feature support
|
|
116
|
+
|
|
117
|
+
# Indicates whether this database supports extensions (like PostgreSQL extensions)
|
|
118
|
+
#
|
|
119
|
+
# @return [Boolean] True if extensions are supported, false otherwise
|
|
120
|
+
def supports_extensions?
|
|
121
|
+
false
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Indicates whether this database supports materialized views
|
|
125
|
+
#
|
|
126
|
+
# @return [Boolean] True if materialized views are supported, false otherwise
|
|
127
|
+
def supports_materialized_views?
|
|
128
|
+
false
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Indicates whether this database supports custom types (enums, composite types)
|
|
132
|
+
#
|
|
133
|
+
# @return [Boolean] True if custom types are supported, false otherwise
|
|
134
|
+
def supports_custom_types?
|
|
135
|
+
false
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Indicates whether this database supports domains
|
|
139
|
+
#
|
|
140
|
+
# @return [Boolean] True if domains are supported, false otherwise
|
|
141
|
+
def supports_domains?
|
|
142
|
+
false
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Indicates whether this database supports stored procedures/functions
|
|
146
|
+
#
|
|
147
|
+
# @return [Boolean] True if functions are supported, false otherwise
|
|
148
|
+
def supports_functions?
|
|
149
|
+
false
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Indicates whether this database supports triggers
|
|
153
|
+
#
|
|
154
|
+
# @return [Boolean] True if triggers are supported, false otherwise
|
|
155
|
+
def supports_triggers?
|
|
156
|
+
false
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Indicates whether this database supports sequences
|
|
160
|
+
#
|
|
161
|
+
# @return [Boolean] True if sequences are supported, false otherwise
|
|
162
|
+
def supports_sequences?
|
|
163
|
+
false
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Version detection
|
|
167
|
+
|
|
168
|
+
# Detect the database version
|
|
169
|
+
#
|
|
170
|
+
# @return [String] Normalized version string (e.g., "14.5" for PostgreSQL 14.5)
|
|
171
|
+
# @raise [NotImplementedError] If not implemented by subclass
|
|
172
|
+
def database_version
|
|
173
|
+
raise NotImplementedError, "#{self.class} must implement #database_version"
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Parse version string from database
|
|
177
|
+
#
|
|
178
|
+
# @param version_string [String] Raw version string from database
|
|
179
|
+
# @return [String] Normalized version (e.g., "14.5")
|
|
180
|
+
# @raise [NotImplementedError] If not implemented by subclass
|
|
181
|
+
def parse_version(version_string)
|
|
182
|
+
raise NotImplementedError, "#{self.class} must implement #parse_version"
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Utility methods for version comparison
|
|
186
|
+
|
|
187
|
+
# Extract major version number
|
|
188
|
+
#
|
|
189
|
+
# @param version_string [String] Version string (e.g., "14.5")
|
|
190
|
+
# @return [Integer] Major version (e.g., 14)
|
|
191
|
+
def major_version(version_string)
|
|
192
|
+
version_string.split('.').first.to_i
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Extract minor version number
|
|
196
|
+
#
|
|
197
|
+
# @param version_string [String] Version string (e.g., "14.5")
|
|
198
|
+
# @return [Integer] Minor version (e.g., 5), or 0 if not present
|
|
199
|
+
def minor_version(version_string)
|
|
200
|
+
parts = version_string.split('.')
|
|
201
|
+
parts.length > 1 ? parts[1].to_i : 0
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Compare two version strings
|
|
205
|
+
#
|
|
206
|
+
# @param version1 [String] First version to compare
|
|
207
|
+
# @param version2 [String] Second version to compare
|
|
208
|
+
# @return [Integer] -1 if version1 < version2, 0 if equal, 1 if version1 > version2
|
|
209
|
+
def compare_versions(version1, version2)
|
|
210
|
+
v1_parts = version1.split('.').map(&:to_i)
|
|
211
|
+
v2_parts = version2.split('.').map(&:to_i)
|
|
212
|
+
|
|
213
|
+
[v1_parts.length, v2_parts.length].max.times do |i|
|
|
214
|
+
v1 = v1_parts[i] || 0
|
|
215
|
+
v2 = v2_parts[i] || 0
|
|
216
|
+
|
|
217
|
+
return -1 if v1 < v2
|
|
218
|
+
return 1 if v1 > v2
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
0
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Check if version meets minimum requirement
|
|
225
|
+
#
|
|
226
|
+
# @param current_version [String] Current database version
|
|
227
|
+
# @param required_version [String] Required minimum version
|
|
228
|
+
# @return [Boolean] True if current version is greater than or equal to required version
|
|
229
|
+
def version_at_least?(current_version, required_version)
|
|
230
|
+
compare_versions(current_version, required_version) >= 0
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|