dbviewer 0.6.3 → 0.6.5

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.
@@ -0,0 +1,173 @@
1
+ module Dbviewer
2
+ module RelationshipManagement
3
+ extend ActiveSupport::Concern
4
+
5
+ # Fetch relationships between tables for ERD visualization
6
+ def fetch_table_relationships(tables)
7
+ tables.flat_map { |table| extract_table_relationships_from_metadata(table[:name]) }
8
+ end
9
+
10
+ # Get mini ERD data for a specific table and its relationships
11
+ def fetch_mini_erd_for_table(table_name)
12
+ outgoing_data = collect_outgoing_relationships(table_name)
13
+ incoming_data = collect_incoming_relationships(table_name)
14
+
15
+ initial_tables = [ { name: table_name } ]
16
+ all_relationships = outgoing_data[:relationships] + incoming_data[:relationships]
17
+ all_tables = (initial_tables + outgoing_data[:tables] + incoming_data[:tables]).uniq { |t| t[:name] }
18
+
19
+ {
20
+ tables: all_tables,
21
+ relationships: all_relationships,
22
+ timestamp: Time.now.to_i
23
+ }
24
+ end
25
+
26
+ private
27
+
28
+ # Extract relationships for a single table from its metadata
29
+ # @param table_name [String] The name of the table to process
30
+ # @return [Array<Hash>] Array of relationship hashes for this table
31
+ def extract_table_relationships_from_metadata(table_name)
32
+ metadata = database_manager.table_metadata(table_name)
33
+ return [] unless metadata&.dig(:foreign_keys)&.present?
34
+
35
+ metadata[:foreign_keys].map do |fk|
36
+ {
37
+ from_table: table_name,
38
+ to_table: fk[:to_table],
39
+ from_column: fk[:column],
40
+ to_column: fk[:primary_key],
41
+ name: fk[:name]
42
+ }
43
+ end
44
+ end
45
+
46
+ # Collect outgoing relationships from the specified table to other tables
47
+ # @param table_name [String] The source table name
48
+ # @return [Hash] Hash containing :tables and :relationships arrays
49
+ def collect_outgoing_relationships(table_name)
50
+ tables = []
51
+ relationships = []
52
+
53
+ metadata = fetch_table_metadata(table_name)
54
+ return { tables: tables, relationships: relationships } unless metadata&.dig(:foreign_keys)&.present?
55
+
56
+ metadata[:foreign_keys].each do |fk|
57
+ result = process_outgoing_foreign_key(table_name, fk)
58
+ if result
59
+ relationships << result[:relationship]
60
+ tables << result[:table]
61
+ end
62
+ end
63
+
64
+ {
65
+ tables: tables,
66
+ relationships: relationships
67
+ }
68
+ end
69
+
70
+ # Process a single outgoing foreign key relationship
71
+ # @param table_name [String] The source table name
72
+ # @param fk [Hash] Foreign key metadata
73
+ # @return [Hash, nil] Hash containing :relationship and :table, or nil if invalid
74
+ def process_outgoing_foreign_key(table_name, fk)
75
+ return nil unless fk[:to_table].present? && fk[:column].present?
76
+
77
+ relationship = build_relationship_hash(
78
+ from_table: table_name.to_s,
79
+ to_table: fk[:to_table].to_s,
80
+ from_column: fk[:column].to_s,
81
+ to_column: fk[:primary_key].to_s.presence || "id",
82
+ name: fk[:name].to_s.presence || "#{table_name}_to_#{fk[:to_table]}",
83
+ direction: "outgoing"
84
+ )
85
+
86
+ {
87
+ relationship: relationship,
88
+ table: {
89
+ name: fk[:to_table].to_s
90
+ }
91
+ }
92
+ end
93
+
94
+ # Collect incoming relationships from other tables to the specified table
95
+ # @param table_name [String] The target table name
96
+ # @return [Hash] Hash containing :tables and :relationships arrays
97
+ def collect_incoming_relationships(table_name)
98
+ results = database_manager.tables
99
+ .reject { |other_table_name| other_table_name == table_name }
100
+ .map { |other_table_name| process_table_for_incoming_relationships(table_name, other_table_name) }
101
+ .compact
102
+
103
+ {
104
+ tables: results.flat_map { |result| result[:tables] },
105
+ relationships: results.flat_map { |result| result[:relationships] }
106
+ }
107
+ end
108
+
109
+ # Process a single table to find incoming relationships to the target table
110
+ # @param target_table [String] The target table name
111
+ # @param source_table [String] The source table name to check
112
+ # @return [Hash, nil] Hash containing :tables and :relationships arrays, or nil if no relationships
113
+ def process_table_for_incoming_relationships(target_table, source_table)
114
+ other_metadata = fetch_table_metadata(source_table)
115
+ return nil unless other_metadata&.dig(:foreign_keys)&.present?
116
+
117
+ results = other_metadata[:foreign_keys]
118
+ .map { |fk| process_incoming_foreign_key(target_table, source_table, fk) }
119
+ .compact
120
+
121
+ return nil if results.empty?
122
+
123
+ {
124
+ tables: results.map { |result| result[:table] },
125
+ relationships: results.map { |result| result[:relationship] }
126
+ }
127
+ end
128
+
129
+ # Process a single incoming foreign key relationship
130
+ # @param target_table [String] The target table name
131
+ # @param source_table [String] The source table name
132
+ # @param fk [Hash] Foreign key metadata
133
+ # @return [Hash, nil] Hash containing :relationship and :table, or nil if invalid
134
+ def process_incoming_foreign_key(target_table, source_table, fk)
135
+ return nil unless fk[:to_table] == target_table && fk[:column].present?
136
+
137
+ relationship = build_relationship_hash(
138
+ from_table: source_table.to_s,
139
+ to_table: target_table.to_s,
140
+ from_column: fk[:column].to_s,
141
+ to_column: fk[:primary_key].to_s.presence || "id",
142
+ name: fk[:name].to_s.presence || "#{source_table}_to_#{target_table}",
143
+ direction: "incoming"
144
+ )
145
+
146
+ {
147
+ relationship: relationship,
148
+ table: {
149
+ name: source_table.to_s
150
+ }
151
+ }
152
+ end
153
+
154
+ # Build a standardized relationship hash
155
+ # @param from_table [String] Source table name
156
+ # @param to_table [String] Target table name
157
+ # @param from_column [String] Source column name
158
+ # @param to_column [String] Target column name
159
+ # @param name [String] Relationship name
160
+ # @param direction [String] Relationship direction
161
+ # @return [Hash] Standardized relationship hash
162
+ def build_relationship_hash(from_table:, to_table:, from_column:, to_column:, name:, direction:)
163
+ {
164
+ from_table: from_table,
165
+ to_table: to_table,
166
+ from_column: from_column,
167
+ to_column: to_column,
168
+ name: name,
169
+ direction: direction
170
+ }
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,56 @@
1
+ module Dbviewer
2
+ module TableOperations
3
+ extend ActiveSupport::Concern
4
+
5
+ # Fetch all tables with their stats
6
+ # By default, don't include record counts for better performance on sidebar
7
+ def fetch_tables(include_record_counts = false)
8
+ database_manager.tables.map do |table_name|
9
+ table_stats = {
10
+ name: table_name
11
+ }
12
+ table_stats[:record_count] = database_manager.record_count(table_name) if include_record_counts
13
+ table_stats
14
+ end
15
+ end
16
+
17
+ # Get column information for a specific table
18
+ def fetch_table_columns(table_name)
19
+ database_manager.table_columns(table_name)
20
+ end
21
+
22
+ # Get the total number of records in a table
23
+ def fetch_table_record_count(table_name)
24
+ database_manager.table_count(table_name)
25
+ end
26
+
27
+ # Fetch records for a table with pagination and sorting
28
+ def fetch_table_records(table_name, query_params)
29
+ database_manager.table_records(table_name, query_params)
30
+ end
31
+
32
+ # Get filtered record count for a table
33
+ def fetch_filtered_record_count(table_name, column_filters)
34
+ database_manager.filtered_record_count(table_name, column_filters)
35
+ end
36
+
37
+ # Safely quote a table name, with fallback
38
+ def safe_quote_table_name(table_name)
39
+ database_manager.connection.quote_table_name(table_name) rescue table_name.to_s
40
+ end
41
+
42
+ # Get table metadata for display (e.g., primary key, foreign keys, indexes)
43
+ def fetch_table_metadata(table_name)
44
+ database_manager.table_metadata(table_name)
45
+ end
46
+
47
+ private
48
+
49
+ # Check if a table has a created_at column for timestamp visualization
50
+ def has_timestamp_column?(table_name)
51
+ fetch_table_columns(table_name).any? do |column|
52
+ column[:name] == "created_at" && [ :datetime, :timestamp ].include?(column[:type])
53
+ end
54
+ end
55
+ end
56
+ end
@@ -4,7 +4,7 @@ module Dbviewer
4
4
  before_action :set_tables
5
5
 
6
6
  def relationships
7
- @table_relationships = fetch_table_relationships
7
+ @table_relationships = fetch_table_relationships(@tables)
8
8
  render_success({
9
9
  relationships: @table_relationships,
10
10
  status: "success"
@@ -56,8 +56,13 @@ module Dbviewer
56
56
  @columns = fetch_table_columns(@table_name)
57
57
  @tables = fetch_tables # Fetch tables for sidebar
58
58
 
59
- prepare_query
60
- execute_query
59
+ @query = prepare_query(@table_name, params[:query])
60
+ @records = begin
61
+ execute_query(@query)
62
+ rescue => e
63
+ @error = "Error executing query: #{e.message}"
64
+ nil
65
+ end
61
66
 
62
67
  render :query
63
68
  end