dbviewer 0.6.3 → 0.6.4
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 +4 -4
- data/README.md +43 -14
- data/app/controllers/concerns/dbviewer/connection_management.rb +88 -0
- data/app/controllers/concerns/dbviewer/data_export.rb +32 -0
- data/app/controllers/concerns/dbviewer/database_information.rb +62 -0
- data/app/controllers/concerns/dbviewer/database_operations.rb +8 -503
- data/app/controllers/concerns/dbviewer/datatable_support.rb +47 -0
- data/app/controllers/concerns/dbviewer/query_operations.rb +28 -0
- data/app/controllers/concerns/dbviewer/relationship_management.rb +173 -0
- data/app/controllers/concerns/dbviewer/table_operations.rb +56 -0
- data/app/controllers/dbviewer/api/entity_relationship_diagrams_controller.rb +1 -1
- data/app/controllers/dbviewer/tables_controller.rb +7 -2
- data/app/helpers/dbviewer/application_helper.rb +9 -541
- data/app/helpers/dbviewer/database_helper.rb +59 -0
- data/app/helpers/dbviewer/filter_helper.rb +137 -0
- data/app/helpers/dbviewer/formatting_helper.rb +30 -0
- data/app/helpers/dbviewer/navigation_helper.rb +35 -0
- data/app/helpers/dbviewer/pagination_helper.rb +72 -0
- data/app/helpers/dbviewer/sorting_helper.rb +47 -0
- data/app/helpers/dbviewer/table_rendering_helper.rb +145 -0
- data/app/helpers/dbviewer/ui_helper.rb +41 -0
- data/lib/dbviewer/database/manager.rb +2 -3
- data/lib/dbviewer/datatable/query_operations.rb +35 -20
- data/lib/dbviewer/query/executor.rb +0 -35
- data/lib/dbviewer/version.rb +1 -1
- metadata +16 -1
@@ -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
|
@@ -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
|
-
|
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
|