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,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterStructureSql
|
|
4
|
+
module Generators
|
|
5
|
+
# Generates CREATE TRIGGER statements
|
|
6
|
+
#
|
|
7
|
+
# Supports BEFORE, AFTER, INSTEAD OF triggers with row/statement timing.
|
|
8
|
+
class TriggerGenerator < Base
|
|
9
|
+
# Generates CREATE TRIGGER statement
|
|
10
|
+
#
|
|
11
|
+
# @param trigger [Hash] Trigger metadata
|
|
12
|
+
# @return [String] SQL statement
|
|
13
|
+
def generate(trigger)
|
|
14
|
+
# PostgreSQL's pg_get_triggerdef returns complete CREATE TRIGGER statement
|
|
15
|
+
# MySQL SHOW CREATE TRIGGER also returns complete statement
|
|
16
|
+
if trigger[:definition]
|
|
17
|
+
definition = trigger[:definition].strip
|
|
18
|
+
|
|
19
|
+
# Strip DEFINER clause for MySQL triggers for portability
|
|
20
|
+
definition = definition.gsub(/CREATE DEFINER=`[^`]+`@`[^`]+`/, 'CREATE') if definition.include?('CREATE DEFINER')
|
|
21
|
+
|
|
22
|
+
# Ensure ends with semicolon for structure.sql
|
|
23
|
+
definition += ';' unless definition.end_with?(';')
|
|
24
|
+
return definition
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# For MySQL/SQLite, generate CREATE TRIGGER from components
|
|
28
|
+
timing = trigger[:timing] || 'AFTER'
|
|
29
|
+
event = trigger[:event] || 'INSERT'
|
|
30
|
+
table_name = quote_identifier(trigger[:table_name])
|
|
31
|
+
trigger_name = quote_identifier(trigger[:name])
|
|
32
|
+
statement = trigger[:statement] || trigger[:body] || ''
|
|
33
|
+
|
|
34
|
+
<<~SQL.strip
|
|
35
|
+
CREATE TRIGGER #{trigger_name}
|
|
36
|
+
#{timing} #{event} ON #{table_name}
|
|
37
|
+
FOR EACH ROW
|
|
38
|
+
BEGIN
|
|
39
|
+
#{statement}
|
|
40
|
+
END;
|
|
41
|
+
SQL
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def quote_identifier(identifier)
|
|
47
|
+
return identifier if identifier.nil?
|
|
48
|
+
|
|
49
|
+
# Use double quotes for SQL standard identifier quoting
|
|
50
|
+
"\"#{identifier}\""
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterStructureSql
|
|
4
|
+
module Generators
|
|
5
|
+
# Generates CREATE TYPE statements for enums and composite types
|
|
6
|
+
class TypeGenerator < Base
|
|
7
|
+
# Generates CREATE TYPE statement
|
|
8
|
+
#
|
|
9
|
+
# @param type [Hash] Type metadata (enum or composite)
|
|
10
|
+
# @return [String, nil] SQL statement or nil if unsupported
|
|
11
|
+
def generate(type)
|
|
12
|
+
case type[:type]
|
|
13
|
+
when 'enum'
|
|
14
|
+
generate_enum(type)
|
|
15
|
+
when 'composite'
|
|
16
|
+
generate_composite(type)
|
|
17
|
+
when 'domain'
|
|
18
|
+
generate_domain(type)
|
|
19
|
+
else
|
|
20
|
+
# Unknown type, skip
|
|
21
|
+
nil
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def generate_enum(type)
|
|
28
|
+
values = type[:values].map { |v| "'#{v}'" }.join(', ')
|
|
29
|
+
"CREATE TYPE IF NOT EXISTS #{type[:name]} AS ENUM (#{values});"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def generate_composite(type)
|
|
33
|
+
# Composite types have attributes
|
|
34
|
+
attrs = type[:attributes].map do |attr|
|
|
35
|
+
"#{attr[:name]} #{attr[:type]}"
|
|
36
|
+
end.join(', ')
|
|
37
|
+
"CREATE TYPE IF NOT EXISTS #{type[:name]} AS (#{attrs});"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def generate_domain(type)
|
|
41
|
+
parts = ["CREATE DOMAIN IF NOT EXISTS #{type[:name]} AS #{type[:base_type]}"]
|
|
42
|
+
parts << type[:constraint] if type[:constraint]
|
|
43
|
+
"#{parts.join(' ')};"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterStructureSql
|
|
4
|
+
module Generators
|
|
5
|
+
# Generates CREATE VIEW statements
|
|
6
|
+
class ViewGenerator < Base
|
|
7
|
+
# Generates CREATE VIEW statement
|
|
8
|
+
#
|
|
9
|
+
# @param view [Hash] View metadata with definition
|
|
10
|
+
# @return [String] SQL statement
|
|
11
|
+
def generate(view)
|
|
12
|
+
# Only add schema prefix for non-default schemas
|
|
13
|
+
# PostgreSQL default: 'public'
|
|
14
|
+
# SQLite default: 'main'
|
|
15
|
+
# MySQL default: current database
|
|
16
|
+
default_schemas = %w[public main]
|
|
17
|
+
schema_prefix = default_schemas.include?(view[:schema]) ? '' : "#{view[:schema]}."
|
|
18
|
+
definition = view[:definition].strip
|
|
19
|
+
|
|
20
|
+
# Ensure definition ends with semicolon
|
|
21
|
+
definition += ';' unless definition.end_with?(';')
|
|
22
|
+
|
|
23
|
+
"CREATE OR REPLACE VIEW #{schema_prefix}#{view[:name]} AS\n#{definition}"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterStructureSql
|
|
4
|
+
module Introspection
|
|
5
|
+
# Introspection module for database extensions
|
|
6
|
+
module Extensions
|
|
7
|
+
# Fetches database extensions
|
|
8
|
+
#
|
|
9
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
10
|
+
# @return [Array<Hash>] Array of extension metadata hashes
|
|
11
|
+
def fetch_extensions(connection)
|
|
12
|
+
adapter = get_adapter(connection)
|
|
13
|
+
adapter.fetch_extensions(connection)
|
|
14
|
+
rescue StandardError => e
|
|
15
|
+
warn "Warning: Failed to fetch extensions: #{e.message}"
|
|
16
|
+
[]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def get_adapter(connection)
|
|
22
|
+
@get_adapter ||= Adapters::Registry.adapter_for(
|
|
23
|
+
connection,
|
|
24
|
+
adapter_override: BetterStructureSql.configuration.adapter
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterStructureSql
|
|
4
|
+
module Introspection
|
|
5
|
+
# Introspection module for foreign key constraints
|
|
6
|
+
module ForeignKeys
|
|
7
|
+
# Fetches foreign key constraints
|
|
8
|
+
#
|
|
9
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
10
|
+
# @return [Array<Hash>] Array of foreign key metadata hashes
|
|
11
|
+
def fetch_foreign_keys(connection)
|
|
12
|
+
adapter = get_adapter(connection)
|
|
13
|
+
adapter.fetch_foreign_keys(connection)
|
|
14
|
+
rescue StandardError => e
|
|
15
|
+
warn "Warning: Failed to fetch foreign keys: #{e.message}"
|
|
16
|
+
[]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def get_adapter(connection)
|
|
22
|
+
@get_adapter ||= Adapters::Registry.adapter_for(
|
|
23
|
+
connection,
|
|
24
|
+
adapter_override: BetterStructureSql.configuration.adapter
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterStructureSql
|
|
4
|
+
module Introspection
|
|
5
|
+
# Introspection module for database functions
|
|
6
|
+
module Functions
|
|
7
|
+
# Fetches database functions and stored procedures
|
|
8
|
+
#
|
|
9
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
10
|
+
# @return [Array<Hash>] Array of function metadata hashes
|
|
11
|
+
def fetch_functions(connection)
|
|
12
|
+
adapter = get_adapter(connection)
|
|
13
|
+
adapter.fetch_functions(connection)
|
|
14
|
+
rescue StandardError => e
|
|
15
|
+
warn "Warning: Failed to fetch functions: #{e.message}"
|
|
16
|
+
[]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def get_adapter(connection)
|
|
22
|
+
@get_adapter ||= Adapters::Registry.adapter_for(
|
|
23
|
+
connection,
|
|
24
|
+
adapter_override: BetterStructureSql.configuration.adapter
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterStructureSql
|
|
4
|
+
module Introspection
|
|
5
|
+
# Introspection module for database indexes
|
|
6
|
+
module Indexes
|
|
7
|
+
# Fetches database indexes
|
|
8
|
+
#
|
|
9
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
10
|
+
# @return [Array<Hash>] Array of index metadata hashes
|
|
11
|
+
def fetch_indexes(connection)
|
|
12
|
+
adapter = get_adapter(connection)
|
|
13
|
+
adapter.fetch_indexes(connection)
|
|
14
|
+
rescue StandardError => e
|
|
15
|
+
warn "Warning: Failed to fetch indexes: #{e.message}"
|
|
16
|
+
[]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def get_adapter(connection)
|
|
22
|
+
@get_adapter ||= Adapters::Registry.adapter_for(
|
|
23
|
+
connection,
|
|
24
|
+
adapter_override: BetterStructureSql.configuration.adapter
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterStructureSql
|
|
4
|
+
module Introspection
|
|
5
|
+
# Introspection module for database sequences
|
|
6
|
+
module Sequences
|
|
7
|
+
# Fetches database sequences
|
|
8
|
+
#
|
|
9
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
10
|
+
# @return [Array<Hash>] Array of sequence metadata hashes
|
|
11
|
+
def fetch_sequences(connection)
|
|
12
|
+
adapter = get_adapter(connection)
|
|
13
|
+
adapter.fetch_sequences(connection)
|
|
14
|
+
rescue StandardError => e
|
|
15
|
+
warn "Warning: Failed to fetch sequences: #{e.message}"
|
|
16
|
+
[]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def get_adapter(connection)
|
|
22
|
+
@get_adapter ||= Adapters::Registry.adapter_for(
|
|
23
|
+
connection,
|
|
24
|
+
adapter_override: BetterStructureSql.configuration.adapter
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterStructureSql
|
|
4
|
+
module Introspection
|
|
5
|
+
# Introspection module for database tables
|
|
6
|
+
module Tables
|
|
7
|
+
# Fetches database tables
|
|
8
|
+
#
|
|
9
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
10
|
+
# @return [Array<Hash>] Array of table metadata hashes
|
|
11
|
+
def fetch_tables(connection)
|
|
12
|
+
adapter = get_adapter(connection)
|
|
13
|
+
adapter.fetch_tables(connection)
|
|
14
|
+
rescue StandardError => e
|
|
15
|
+
warn "Warning: Failed to fetch tables: #{e.message}"
|
|
16
|
+
[]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def get_adapter(connection)
|
|
22
|
+
@get_adapter ||= Adapters::Registry.adapter_for(
|
|
23
|
+
connection,
|
|
24
|
+
adapter_override: BetterStructureSql.configuration.adapter
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterStructureSql
|
|
4
|
+
module Introspection
|
|
5
|
+
# Introspection module for database triggers
|
|
6
|
+
module Triggers
|
|
7
|
+
# Fetches database triggers
|
|
8
|
+
#
|
|
9
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
10
|
+
# @return [Array<Hash>] Array of trigger metadata hashes
|
|
11
|
+
def fetch_triggers(connection)
|
|
12
|
+
adapter = get_adapter(connection)
|
|
13
|
+
adapter.fetch_triggers(connection)
|
|
14
|
+
rescue StandardError => e
|
|
15
|
+
warn "Warning: Failed to fetch triggers: #{e.message}"
|
|
16
|
+
[]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def get_adapter(connection)
|
|
22
|
+
@get_adapter ||= Adapters::Registry.adapter_for(
|
|
23
|
+
connection,
|
|
24
|
+
adapter_override: BetterStructureSql.configuration.adapter
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterStructureSql
|
|
4
|
+
module Introspection
|
|
5
|
+
# Introspection module for custom database types
|
|
6
|
+
module Types
|
|
7
|
+
# Fetches custom database types (enums, domains, composite)
|
|
8
|
+
#
|
|
9
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
10
|
+
# @return [Array<Hash>] Array of type metadata hashes
|
|
11
|
+
def fetch_custom_types(connection)
|
|
12
|
+
adapter = get_adapter(connection)
|
|
13
|
+
adapter.fetch_custom_types(connection)
|
|
14
|
+
rescue StandardError => e
|
|
15
|
+
warn "Warning: Failed to fetch custom types: #{e.message}"
|
|
16
|
+
[]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Fetches enum types only
|
|
20
|
+
#
|
|
21
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
22
|
+
# @return [Array<Hash>] Array of enum type metadata hashes
|
|
23
|
+
def fetch_enums(connection)
|
|
24
|
+
fetch_custom_types(connection).select { |t| t[:type] == 'enum' }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def get_adapter(connection)
|
|
30
|
+
@get_adapter ||= Adapters::Registry.adapter_for(
|
|
31
|
+
connection,
|
|
32
|
+
adapter_override: BetterStructureSql.configuration.adapter
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterStructureSql
|
|
4
|
+
module Introspection
|
|
5
|
+
# Introspection module for database views
|
|
6
|
+
module Views
|
|
7
|
+
# Fetches database views
|
|
8
|
+
#
|
|
9
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
10
|
+
# @return [Array<Hash>] Array of view metadata hashes
|
|
11
|
+
def fetch_views(connection)
|
|
12
|
+
adapter = get_adapter(connection)
|
|
13
|
+
adapter.fetch_views(connection)
|
|
14
|
+
rescue StandardError => e
|
|
15
|
+
warn "Warning: Failed to fetch views: #{e.message}"
|
|
16
|
+
[]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Fetches materialized views
|
|
20
|
+
#
|
|
21
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
22
|
+
# @return [Array<Hash>] Array of materialized view metadata hashes
|
|
23
|
+
def fetch_materialized_views(connection)
|
|
24
|
+
adapter = get_adapter(connection)
|
|
25
|
+
adapter.fetch_materialized_views(connection)
|
|
26
|
+
rescue StandardError => e
|
|
27
|
+
warn "Warning: Failed to fetch materialized views: #{e.message}"
|
|
28
|
+
[]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def get_adapter(connection)
|
|
34
|
+
@get_adapter ||= Adapters::Registry.adapter_for(
|
|
35
|
+
connection,
|
|
36
|
+
adapter_override: BetterStructureSql.configuration.adapter
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'introspection/extensions'
|
|
4
|
+
require_relative 'introspection/sequences'
|
|
5
|
+
require_relative 'introspection/types'
|
|
6
|
+
require_relative 'introspection/tables'
|
|
7
|
+
require_relative 'introspection/indexes'
|
|
8
|
+
require_relative 'introspection/foreign_keys'
|
|
9
|
+
require_relative 'introspection/views'
|
|
10
|
+
require_relative 'introspection/functions'
|
|
11
|
+
require_relative 'introspection/triggers'
|
|
12
|
+
|
|
13
|
+
module BetterStructureSql
|
|
14
|
+
# Introspection facade for database metadata extraction
|
|
15
|
+
#
|
|
16
|
+
# Provides a unified interface for querying database objects across
|
|
17
|
+
# all supported adapters (PostgreSQL, MySQL, SQLite).
|
|
18
|
+
module Introspection
|
|
19
|
+
class << self
|
|
20
|
+
include Extensions
|
|
21
|
+
include Sequences
|
|
22
|
+
include Types
|
|
23
|
+
include Tables
|
|
24
|
+
include Indexes
|
|
25
|
+
include ForeignKeys
|
|
26
|
+
include Views
|
|
27
|
+
include Functions
|
|
28
|
+
include Triggers
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module BetterStructureSql
|
|
6
|
+
# Generates manifest JSON files for multi-file schema dumps
|
|
7
|
+
# Provides statistics and metadata about the schema files
|
|
8
|
+
class ManifestGenerator
|
|
9
|
+
attr_reader :config
|
|
10
|
+
|
|
11
|
+
def initialize(config = BetterStructureSql.configuration)
|
|
12
|
+
@config = config
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Generate JSON manifest from file map
|
|
16
|
+
# @param file_map [Hash] Map of relative_path => content
|
|
17
|
+
# @return [String] JSON manifest string
|
|
18
|
+
def generate(file_map)
|
|
19
|
+
manifest = {
|
|
20
|
+
version: '1.0',
|
|
21
|
+
total_files: file_map.size,
|
|
22
|
+
total_lines: calculate_total_lines(file_map),
|
|
23
|
+
max_lines_per_file: config.max_lines_per_file,
|
|
24
|
+
directories: calculate_directory_stats(file_map)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
JSON.pretty_generate(manifest)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Parse manifest JSON string
|
|
31
|
+
# @param json_string [String] The JSON manifest
|
|
32
|
+
# @return [Hash] Parsed manifest data
|
|
33
|
+
def parse(json_string)
|
|
34
|
+
JSON.parse(json_string, symbolize_names: true)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
# Calculate total lines across all files
|
|
40
|
+
# @param file_map [Hash] Map of relative_path => content
|
|
41
|
+
# @return [Integer] Total line count
|
|
42
|
+
def calculate_total_lines(file_map)
|
|
43
|
+
file_map.values.sum { |content| content.lines.count }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Calculate statistics per directory
|
|
47
|
+
# @param file_map [Hash] Map of relative_path => content
|
|
48
|
+
# @return [Hash] Directory name => {files:, lines:}
|
|
49
|
+
def calculate_directory_stats(file_map)
|
|
50
|
+
stats = Hash.new { |h, k| h[k] = { files: 0, lines: 0 } }
|
|
51
|
+
|
|
52
|
+
file_map.each do |path, content|
|
|
53
|
+
# Extract directory name (e.g., "1_extensions" from "1_extensions/000001.sql")
|
|
54
|
+
directory = path.split('/').first
|
|
55
|
+
next unless directory
|
|
56
|
+
|
|
57
|
+
stats[directory][:files] += 1
|
|
58
|
+
stats[directory][:lines] += content.lines.count
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Sort by directory name to ensure consistent ordering
|
|
62
|
+
stats.sort.to_h
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|