dbviewer 0.7.10 → 0.7.11
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 +1 -0
- data/app/assets/images/dbviewer/emoji-favicon.txt +1 -0
- data/app/assets/images/dbviewer/favicon.ico +4 -0
- data/app/assets/images/dbviewer/favicon.png +4 -0
- data/app/assets/images/dbviewer/favicon.svg +10 -0
- data/app/assets/javascripts/dbviewer/entity_relationship_diagram.js +38 -42
- data/app/assets/javascripts/dbviewer/error_handler.js +58 -0
- data/app/assets/javascripts/dbviewer/home.js +25 -34
- data/app/assets/javascripts/dbviewer/layout.js +100 -129
- data/app/assets/javascripts/dbviewer/query.js +309 -246
- data/app/assets/javascripts/dbviewer/sidebar.js +170 -183
- data/app/assets/javascripts/dbviewer/utility.js +124 -0
- data/app/assets/stylesheets/dbviewer/application.css +8 -146
- data/app/assets/stylesheets/dbviewer/entity_relationship_diagram.css +0 -34
- data/app/assets/stylesheets/dbviewer/logs.css +0 -11
- data/app/assets/stylesheets/dbviewer/query.css +21 -9
- data/app/assets/stylesheets/dbviewer/table.css +49 -131
- data/app/controllers/concerns/dbviewer/database_operations/connection_management.rb +90 -0
- data/app/controllers/concerns/dbviewer/database_operations/data_export.rb +31 -0
- data/app/controllers/concerns/dbviewer/database_operations/database_information.rb +54 -0
- data/app/controllers/concerns/dbviewer/database_operations/datatable_operations.rb +37 -0
- data/app/controllers/concerns/dbviewer/database_operations/query_operations.rb +37 -0
- data/app/controllers/concerns/dbviewer/database_operations/relationship_management.rb +175 -0
- data/app/controllers/concerns/dbviewer/database_operations/table_operations.rb +46 -0
- data/app/controllers/concerns/dbviewer/database_operations.rb +4 -9
- data/app/controllers/dbviewer/api/tables_controller.rb +12 -0
- data/app/controllers/dbviewer/entity_relationship_diagrams_controller.rb +0 -15
- data/app/controllers/dbviewer/tables_controller.rb +4 -33
- data/app/views/dbviewer/entity_relationship_diagrams/index.html.erb +1 -1
- data/app/views/dbviewer/tables/query.html.erb +21 -6
- data/app/views/dbviewer/tables/show.html.erb +2 -2
- data/app/views/layouts/dbviewer/application.html.erb +12 -3
- data/config/routes.rb +2 -2
- data/lib/dbviewer/database/manager.rb +2 -2
- data/lib/dbviewer/datatable/query_operations.rb +1 -17
- data/lib/dbviewer/engine.rb +29 -0
- data/lib/dbviewer/version.rb +1 -1
- metadata +15 -10
- data/app/controllers/concerns/dbviewer/connection_management.rb +0 -88
- data/app/controllers/concerns/dbviewer/data_export.rb +0 -32
- data/app/controllers/concerns/dbviewer/database_information.rb +0 -62
- data/app/controllers/concerns/dbviewer/datatable_support.rb +0 -47
- data/app/controllers/concerns/dbviewer/pagination_concern.rb +0 -34
- data/app/controllers/concerns/dbviewer/query_operations.rb +0 -28
- data/app/controllers/concerns/dbviewer/relationship_management.rb +0 -173
- data/app/controllers/concerns/dbviewer/table_operations.rb +0 -56
data/config/routes.rb
CHANGED
@@ -4,7 +4,6 @@ Dbviewer::Engine.routes.draw do
|
|
4
4
|
get "query"
|
5
5
|
post "query"
|
6
6
|
get "export_csv"
|
7
|
-
get "mini_erd"
|
8
7
|
end
|
9
8
|
end
|
10
9
|
|
@@ -26,13 +25,14 @@ Dbviewer::Engine.routes.draw do
|
|
26
25
|
get "dashboard", to: "home#index", as: :dashboard
|
27
26
|
|
28
27
|
namespace :api do
|
29
|
-
resources :tables, only: [ :index ] do
|
28
|
+
resources :tables, only: [ :index, :show ] do
|
30
29
|
collection do
|
31
30
|
get "records"
|
32
31
|
get "relationships_count"
|
33
32
|
end
|
34
33
|
member do
|
35
34
|
get "relationship_counts"
|
35
|
+
get "mini_erd"
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
@@ -75,8 +75,8 @@ module Dbviewer
|
|
75
75
|
# @param table_name [String] Name of the table
|
76
76
|
# @param column_filters [Hash] Hash of column_name => filter_value for filtering
|
77
77
|
# @return [Integer] Number of filtered records
|
78
|
-
def
|
79
|
-
@table_query_operations.
|
78
|
+
def table_record_count(table_name, column_filters = {})
|
79
|
+
@table_query_operations.table_record_count(table_name, column_filters)
|
80
80
|
end
|
81
81
|
|
82
82
|
# Get the number of columns in a table
|
@@ -48,9 +48,6 @@ module Dbviewer
|
|
48
48
|
|
49
49
|
# Format results
|
50
50
|
to_result_set(records, column_names)
|
51
|
-
rescue => e
|
52
|
-
Rails.logger.error("[DBViewer] Error executing table query: #{e.message}")
|
53
|
-
raise e
|
54
51
|
end
|
55
52
|
|
56
53
|
# Get the total count of records in a table
|
@@ -58,23 +55,13 @@ module Dbviewer
|
|
58
55
|
# @return [Integer] Number of records
|
59
56
|
def table_count(table_name)
|
60
57
|
get_model_for(table_name).count
|
61
|
-
rescue => e
|
62
|
-
Rails.logger.error("[DBViewer] Error counting records in table #{table_name}: #{e.message}")
|
63
|
-
0
|
64
|
-
end
|
65
|
-
|
66
|
-
# Get the number of records in a table (alias for table_count)
|
67
|
-
# @param table_name [String] Name of the table
|
68
|
-
# @return [Integer] Number of records
|
69
|
-
def record_count(table_name)
|
70
|
-
table_count(table_name)
|
71
58
|
end
|
72
59
|
|
73
60
|
# Get the number of records in a table with filters applied
|
74
61
|
# @param table_name [String] Name of the table
|
75
62
|
# @param column_filters [Hash] Hash of column_name => filter_value for filtering
|
76
63
|
# @return [Integer] Number of filtered records
|
77
|
-
def
|
64
|
+
def table_record_count(table_name, column_filters = {})
|
78
65
|
return table_count(table_name) unless column_filters.present?
|
79
66
|
|
80
67
|
model = get_model_for(table_name)
|
@@ -83,9 +70,6 @@ module Dbviewer
|
|
83
70
|
# Apply filters in the same way as table_records
|
84
71
|
query = apply_column_filters(query, table_name, column_filters)
|
85
72
|
query.count
|
86
|
-
rescue => e
|
87
|
-
Rails.logger.error("[DBViewer] Error counting filtered records in table #{table_name}: #{e.message}")
|
88
|
-
0
|
89
73
|
end
|
90
74
|
|
91
75
|
## -- Delegator
|
data/lib/dbviewer/engine.rb
CHANGED
@@ -15,5 +15,34 @@ module Dbviewer
|
|
15
15
|
initializer "dbviewer.notifications" do
|
16
16
|
Dbviewer::Query::NotificationSubscriber.subscribe
|
17
17
|
end
|
18
|
+
|
19
|
+
# Configure assets for the engine
|
20
|
+
initializer "dbviewer.assets" do |app|
|
21
|
+
# Add engine assets path to asset load paths
|
22
|
+
app.config.assets.paths << root.join("app", "assets", "javascripts").to_s
|
23
|
+
app.config.assets.paths << root.join("app", "assets", "stylesheets").to_s
|
24
|
+
app.config.assets.paths << root.join("app", "assets", "images").to_s
|
25
|
+
|
26
|
+
# Make sure we precompile individual JavaScript files
|
27
|
+
app.config.assets.precompile += %w[
|
28
|
+
dbviewer/utility.js
|
29
|
+
dbviewer/error_handler.js
|
30
|
+
dbviewer/home.js
|
31
|
+
dbviewer/layout.js
|
32
|
+
dbviewer/sidebar.js
|
33
|
+
dbviewer/query.js
|
34
|
+
dbviewer/entity_relationship_diagram.js
|
35
|
+
dbviewer/table.js
|
36
|
+
]
|
37
|
+
|
38
|
+
# Make sure stylesheets are precompiled too
|
39
|
+
app.config.assets.precompile += %w[
|
40
|
+
dbviewer/application.css
|
41
|
+
dbviewer/home.css
|
42
|
+
dbviewer/query.css
|
43
|
+
dbviewer/table.css
|
44
|
+
dbviewer/entity_relationship_diagram.css
|
45
|
+
]
|
46
|
+
end
|
18
47
|
end
|
19
48
|
end
|
data/lib/dbviewer/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dbviewer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wailan Tirajoh
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-06-
|
11
|
+
date: 2025-06-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -77,28 +77,33 @@ files:
|
|
77
77
|
- MIT-LICENSE
|
78
78
|
- README.md
|
79
79
|
- Rakefile
|
80
|
+
- app/assets/images/dbviewer/emoji-favicon.txt
|
81
|
+
- app/assets/images/dbviewer/favicon.ico
|
82
|
+
- app/assets/images/dbviewer/favicon.png
|
83
|
+
- app/assets/images/dbviewer/favicon.svg
|
80
84
|
- app/assets/javascripts/dbviewer/entity_relationship_diagram.js
|
85
|
+
- app/assets/javascripts/dbviewer/error_handler.js
|
81
86
|
- app/assets/javascripts/dbviewer/home.js
|
82
87
|
- app/assets/javascripts/dbviewer/layout.js
|
83
88
|
- app/assets/javascripts/dbviewer/query.js
|
84
89
|
- app/assets/javascripts/dbviewer/sidebar.js
|
85
90
|
- app/assets/javascripts/dbviewer/table.js
|
91
|
+
- app/assets/javascripts/dbviewer/utility.js
|
86
92
|
- app/assets/stylesheets/dbviewer/application.css
|
87
93
|
- app/assets/stylesheets/dbviewer/entity_relationship_diagram.css
|
88
94
|
- app/assets/stylesheets/dbviewer/home.css
|
89
95
|
- app/assets/stylesheets/dbviewer/logs.css
|
90
96
|
- app/assets/stylesheets/dbviewer/query.css
|
91
97
|
- app/assets/stylesheets/dbviewer/table.css
|
92
|
-
- app/controllers/concerns/dbviewer/connection_management.rb
|
93
|
-
- app/controllers/concerns/dbviewer/data_export.rb
|
94
|
-
- app/controllers/concerns/dbviewer/database_information.rb
|
95
98
|
- app/controllers/concerns/dbviewer/database_operations.rb
|
96
|
-
- app/controllers/concerns/dbviewer/
|
99
|
+
- app/controllers/concerns/dbviewer/database_operations/connection_management.rb
|
100
|
+
- app/controllers/concerns/dbviewer/database_operations/data_export.rb
|
101
|
+
- app/controllers/concerns/dbviewer/database_operations/database_information.rb
|
102
|
+
- app/controllers/concerns/dbviewer/database_operations/datatable_operations.rb
|
103
|
+
- app/controllers/concerns/dbviewer/database_operations/query_operations.rb
|
104
|
+
- app/controllers/concerns/dbviewer/database_operations/relationship_management.rb
|
105
|
+
- app/controllers/concerns/dbviewer/database_operations/table_operations.rb
|
97
106
|
- app/controllers/concerns/dbviewer/disabled_state_validation.rb
|
98
|
-
- app/controllers/concerns/dbviewer/pagination_concern.rb
|
99
|
-
- app/controllers/concerns/dbviewer/query_operations.rb
|
100
|
-
- app/controllers/concerns/dbviewer/relationship_management.rb
|
101
|
-
- app/controllers/concerns/dbviewer/table_operations.rb
|
102
107
|
- app/controllers/dbviewer/api/base_controller.rb
|
103
108
|
- app/controllers/dbviewer/api/database_controller.rb
|
104
109
|
- app/controllers/dbviewer/api/entity_relationship_diagrams_controller.rb
|
@@ -1,88 +0,0 @@
|
|
1
|
-
module Dbviewer
|
2
|
-
module ConnectionManagement
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
|
5
|
-
included do
|
6
|
-
helper_method :current_connection_key, :available_connections if respond_to?(:helper_method)
|
7
|
-
end
|
8
|
-
|
9
|
-
# Get the current active connection key
|
10
|
-
def current_connection_key
|
11
|
-
key = session[:dbviewer_connection] || Dbviewer.configuration.current_connection
|
12
|
-
return key.to_sym if key && Dbviewer.configuration.database_connections.key?(key.to_sym)
|
13
|
-
|
14
|
-
first_key = Dbviewer.configuration.database_connections.keys.first
|
15
|
-
if first_key
|
16
|
-
session[:dbviewer_connection] = first_key
|
17
|
-
return first_key
|
18
|
-
end
|
19
|
-
|
20
|
-
:default
|
21
|
-
end
|
22
|
-
|
23
|
-
# Set the current connection to use
|
24
|
-
def switch_connection(connection_key)
|
25
|
-
connection_key = connection_key.to_sym if connection_key.respond_to?(:to_sym)
|
26
|
-
|
27
|
-
if connection_key && Dbviewer.configuration.database_connections.key?(connection_key)
|
28
|
-
session[:dbviewer_connection] = connection_key
|
29
|
-
# Clear the database manager to force it to be recreated with the new connection
|
30
|
-
@database_manager = nil
|
31
|
-
return true
|
32
|
-
else
|
33
|
-
# If the connection key doesn't exist, reset to default connection
|
34
|
-
if Dbviewer.configuration.database_connections.key?(Dbviewer.configuration.current_connection)
|
35
|
-
session[:dbviewer_connection] = Dbviewer.configuration.current_connection
|
36
|
-
@database_manager = nil
|
37
|
-
return true
|
38
|
-
else
|
39
|
-
# If even the default connection isn't valid, try the first available connection
|
40
|
-
first_key = Dbviewer.configuration.database_connections.keys.first
|
41
|
-
if first_key
|
42
|
-
session[:dbviewer_connection] = first_key
|
43
|
-
@database_manager = nil
|
44
|
-
return true
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
false # Return false if we couldn't set a valid connection
|
50
|
-
end
|
51
|
-
|
52
|
-
# Get list of available connections
|
53
|
-
def available_connections
|
54
|
-
connections = Dbviewer.configuration.database_connections.map do |key, config|
|
55
|
-
# Try to determine the adapter name if it's not already stored
|
56
|
-
adapter_name = nil
|
57
|
-
if config[:adapter_name].present?
|
58
|
-
adapter_name = config[:adapter_name]
|
59
|
-
elsif config[:connection].present?
|
60
|
-
begin
|
61
|
-
adapter_name = config[:connection].connection.adapter_name
|
62
|
-
rescue => e
|
63
|
-
Rails.logger.error("Error getting adapter name: #{e.message}")
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
{
|
68
|
-
key: key,
|
69
|
-
name: config[:name] || key.to_s.humanize,
|
70
|
-
adapter_name: adapter_name,
|
71
|
-
current: key.to_sym == current_connection_key.to_sym
|
72
|
-
}
|
73
|
-
end
|
74
|
-
|
75
|
-
# Ensure at least one connection is marked as current
|
76
|
-
unless connections.any? { |c| c[:current] }
|
77
|
-
# If no connection is current, mark the first one as current
|
78
|
-
if connections.any?
|
79
|
-
connections.first[:current] = true
|
80
|
-
# Also update the session
|
81
|
-
session[:dbviewer_connection] = connections.first[:key]
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
connections
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
require "csv"
|
2
|
-
|
3
|
-
module Dbviewer
|
4
|
-
module DataExport
|
5
|
-
extend ActiveSupport::Concern
|
6
|
-
|
7
|
-
# Export table data to CSV
|
8
|
-
def export_table_to_csv(table_name, query_params = nil, include_headers = true)
|
9
|
-
records = database_manager.table_query_operations.table_records(table_name, query_params)
|
10
|
-
|
11
|
-
csv_data = CSV.generate do |csv|
|
12
|
-
# Add headers if requested
|
13
|
-
csv << records.columns if include_headers
|
14
|
-
|
15
|
-
# Add rows
|
16
|
-
records.rows.each do |row|
|
17
|
-
csv << row.map { |cell| format_csv_value(cell) }
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
csv_data
|
22
|
-
end
|
23
|
-
|
24
|
-
private
|
25
|
-
|
26
|
-
# Format cell values for CSV export to handle nil values and special characters
|
27
|
-
def format_csv_value(value)
|
28
|
-
return "" if value.nil?
|
29
|
-
value.to_s
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
@@ -1,62 +0,0 @@
|
|
1
|
-
module Dbviewer
|
2
|
-
module DatabaseInformation
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
|
5
|
-
included do
|
6
|
-
helper_method :get_database_name, :get_adapter_name if respond_to?(:helper_method)
|
7
|
-
end
|
8
|
-
|
9
|
-
# Get the name of the current database
|
10
|
-
def get_database_name
|
11
|
-
# First check if this connection has a name in the configuration
|
12
|
-
current_conn_config = Dbviewer.configuration.database_connections[current_connection_key]
|
13
|
-
return current_conn_config[:name] if current_conn_config && current_conn_config[:name].present?
|
14
|
-
|
15
|
-
adapter = database_manager.connection.adapter_name.downcase
|
16
|
-
|
17
|
-
case adapter
|
18
|
-
when /mysql/
|
19
|
-
query = "SELECT DATABASE() as db_name"
|
20
|
-
result = database_manager.execute_query(query).first
|
21
|
-
result ? result["db_name"] : "Database"
|
22
|
-
when /postgres/
|
23
|
-
query = "SELECT current_database() as db_name"
|
24
|
-
result = database_manager.execute_query(query).first
|
25
|
-
result ? result["db_name"] : "Database"
|
26
|
-
when /sqlite/
|
27
|
-
# For SQLite, extract the database name from the connection_config
|
28
|
-
database_path = database_manager.connection.pool.spec.config[:database] || ""
|
29
|
-
File.basename(database_path, ".*") || "SQLite Database"
|
30
|
-
else
|
31
|
-
"Database" # Default fallback
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
# Get the name of the current database adapter
|
36
|
-
def get_adapter_name
|
37
|
-
adapter_name = database_manager.connection.adapter_name.downcase
|
38
|
-
adapter_mappings = {
|
39
|
-
/mysql/i => "MySQL",
|
40
|
-
/postgres/i => "PostgreSQL",
|
41
|
-
/sqlite/i => "SQLite",
|
42
|
-
/oracle/i => "Oracle",
|
43
|
-
/sqlserver|mssql/i => "SQL Server"
|
44
|
-
}
|
45
|
-
adapter_mappings.find { |pattern, _| adapter_name =~ pattern }&.last || adapter_name.titleize
|
46
|
-
rescue
|
47
|
-
"Unknown"
|
48
|
-
end
|
49
|
-
|
50
|
-
# Gather database analytics information
|
51
|
-
def fetch_database_analytics
|
52
|
-
tables = fetch_tables(include_record_counts: true)
|
53
|
-
|
54
|
-
{
|
55
|
-
total_tables: tables.size,
|
56
|
-
total_records: tables.sum { |t| t[:record_count] },
|
57
|
-
largest_tables: tables.sort_by { |t| -t[:record_count] }.first(10),
|
58
|
-
empty_tables: tables.select { |t| t[:record_count] == 0 }
|
59
|
-
}
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
@@ -1,47 +0,0 @@
|
|
1
|
-
module Dbviewer
|
2
|
-
module DatatableSupport
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
|
5
|
-
# Consolidated method to fetch all datatable-related data in one call
|
6
|
-
# Returns a hash containing all necessary datatable information
|
7
|
-
def fetch_datatable_data(table_name, query_params)
|
8
|
-
columns = fetch_table_columns(table_name)
|
9
|
-
return default_datatable_structure(table_name) if columns.empty?
|
10
|
-
|
11
|
-
if query_params.column_filters.empty?
|
12
|
-
total_count = fetch_table_record_count(table_name)
|
13
|
-
else
|
14
|
-
total_count = fetch_filtered_record_count(table_name, query_params.column_filters)
|
15
|
-
end
|
16
|
-
|
17
|
-
{
|
18
|
-
columns: columns,
|
19
|
-
records: fetch_table_records(table_name, query_params),
|
20
|
-
total_count: total_count,
|
21
|
-
total_pages: total_count > 0 ? (total_count.to_f / query_params.per_page).ceil : 0,
|
22
|
-
metadata: fetch_table_metadata(table_name),
|
23
|
-
current_page: query_params.page,
|
24
|
-
per_page: query_params.per_page,
|
25
|
-
order_by: query_params.order_by,
|
26
|
-
direction: query_params.direction
|
27
|
-
}
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
# Default structure for tables with no data/columns
|
33
|
-
def default_datatable_structure(table_name)
|
34
|
-
{
|
35
|
-
columns: [],
|
36
|
-
records: [],
|
37
|
-
total_count: 0,
|
38
|
-
total_pages: 0,
|
39
|
-
metadata: {},
|
40
|
-
current_page: 1,
|
41
|
-
per_page: 25,
|
42
|
-
order_by: nil,
|
43
|
-
direction: "ASC"
|
44
|
-
}
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
@@ -1,34 +0,0 @@
|
|
1
|
-
module Dbviewer
|
2
|
-
module PaginationConcern
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
|
5
|
-
included do
|
6
|
-
# Sort direction validation
|
7
|
-
const_set(:VALID_SORT_DIRECTIONS, %w[ASC DESC].freeze) unless const_defined?(:VALID_SORT_DIRECTIONS)
|
8
|
-
end
|
9
|
-
|
10
|
-
module ClassMethods
|
11
|
-
# Use configuration values from Dbviewer module
|
12
|
-
def per_page_options
|
13
|
-
Dbviewer.configuration.per_page_options
|
14
|
-
end
|
15
|
-
|
16
|
-
def default_per_page
|
17
|
-
Dbviewer.configuration.default_per_page
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def fetch_total_count(table_name, query_params)
|
22
|
-
if query_params.column_filters.present? && query_params.column_filters.values.any?(&:present?)
|
23
|
-
fetch_filtered_record_count(table_name, query_params.column_filters)
|
24
|
-
else
|
25
|
-
fetch_table_record_count(table_name)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
# Calculate the total number of pages
|
30
|
-
def calculate_total_pages(total_count, per_page)
|
31
|
-
(total_count.to_f / per_page).ceil
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
module Dbviewer
|
2
|
-
module QueryOperations
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
|
5
|
-
# Prepare the SQL query - either from params or default
|
6
|
-
def prepare_query(table_name, query)
|
7
|
-
query = query.present? ? query.to_s : default_query(table_name)
|
8
|
-
|
9
|
-
# Validate query for security
|
10
|
-
unless ::Dbviewer::Validator::Sql.safe_query?(query)
|
11
|
-
query = default_query(table_name)
|
12
|
-
flash.now[:warning] = "Only SELECT queries are allowed. Your query contained potentially unsafe operations. Using default query instead."
|
13
|
-
end
|
14
|
-
|
15
|
-
query
|
16
|
-
end
|
17
|
-
|
18
|
-
# Execute the prepared SQL query
|
19
|
-
def execute_query(query)
|
20
|
-
database_manager.execute_query(@query)
|
21
|
-
end
|
22
|
-
|
23
|
-
def default_query(table_name)
|
24
|
-
quoted_table = safe_quote_table_name(table_name)
|
25
|
-
"SELECT * FROM #{quoted_table} LIMIT 100"
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
@@ -1,173 +0,0 @@
|
|
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
|