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
@@ -1,100 +1,16 @@
|
|
1
|
-
require "csv"
|
2
|
-
|
3
1
|
module Dbviewer
|
4
2
|
module DatabaseOperations
|
5
3
|
extend ActiveSupport::Concern
|
6
4
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
# Get the connection key from the session or fall back to the default
|
15
|
-
key = session[:dbviewer_connection] || Dbviewer.configuration.current_connection
|
16
|
-
|
17
|
-
# Ensure the key actually exists in our configured connections
|
18
|
-
if key && Dbviewer.configuration.database_connections.key?(key.to_sym)
|
19
|
-
return key.to_sym
|
20
|
-
end
|
21
|
-
|
22
|
-
# If the key doesn't exist, fall back to any available connection
|
23
|
-
first_key = Dbviewer.configuration.database_connections.keys.first
|
24
|
-
if first_key
|
25
|
-
session[:dbviewer_connection] = first_key # Update the session
|
26
|
-
return first_key
|
27
|
-
end
|
28
|
-
|
29
|
-
# If there are no connections configured, use a default key
|
30
|
-
# This should never happen in normal operation, but it's a safety measure
|
31
|
-
:default
|
32
|
-
end
|
33
|
-
|
34
|
-
# Set the current connection to use
|
35
|
-
def switch_connection(connection_key)
|
36
|
-
connection_key = connection_key.to_sym if connection_key.respond_to?(:to_sym)
|
37
|
-
|
38
|
-
if connection_key && Dbviewer.configuration.database_connections.key?(connection_key)
|
39
|
-
session[:dbviewer_connection] = connection_key
|
40
|
-
# Clear the database manager to force it to be recreated with the new connection
|
41
|
-
@database_manager = nil
|
42
|
-
return true
|
43
|
-
else
|
44
|
-
# If the connection key doesn't exist, reset to default connection
|
45
|
-
if Dbviewer.configuration.database_connections.key?(Dbviewer.configuration.current_connection)
|
46
|
-
session[:dbviewer_connection] = Dbviewer.configuration.current_connection
|
47
|
-
@database_manager = nil
|
48
|
-
return true
|
49
|
-
else
|
50
|
-
# If even the default connection isn't valid, try the first available connection
|
51
|
-
first_key = Dbviewer.configuration.database_connections.keys.first
|
52
|
-
if first_key
|
53
|
-
session[:dbviewer_connection] = first_key
|
54
|
-
@database_manager = nil
|
55
|
-
return true
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
false # Return false if we couldn't set a valid connection
|
61
|
-
end
|
62
|
-
|
63
|
-
# Get list of available connections
|
64
|
-
def available_connections
|
65
|
-
connections = Dbviewer.configuration.database_connections.map do |key, config|
|
66
|
-
# Try to determine the adapter name if it's not already stored
|
67
|
-
adapter_name = nil
|
68
|
-
if config[:adapter_name].present?
|
69
|
-
adapter_name = config[:adapter_name]
|
70
|
-
elsif config[:connection].present?
|
71
|
-
begin
|
72
|
-
adapter_name = config[:connection].connection.adapter_name
|
73
|
-
rescue => e
|
74
|
-
Rails.logger.error("Error getting adapter name: #{e.message}")
|
75
|
-
end
|
76
|
-
end
|
5
|
+
include ConnectionManagement
|
6
|
+
include DatabaseInformation
|
7
|
+
include TableOperations
|
8
|
+
include RelationshipManagement
|
9
|
+
include QueryOperations
|
10
|
+
include DataExport
|
11
|
+
include DatatableSupport
|
77
12
|
|
78
|
-
|
79
|
-
key: key,
|
80
|
-
name: config[:name] || key.to_s.humanize,
|
81
|
-
adapter_name: adapter_name,
|
82
|
-
current: key.to_sym == current_connection_key.to_sym
|
83
|
-
}
|
84
|
-
end
|
85
|
-
|
86
|
-
# Ensure at least one connection is marked as current
|
87
|
-
unless connections.any? { |c| c[:current] }
|
88
|
-
# If no connection is current, mark the first one as current
|
89
|
-
if connections.any?
|
90
|
-
connections.first[:current] = true
|
91
|
-
# Also update the session
|
92
|
-
session[:dbviewer_connection] = connections.first[:key]
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
connections
|
97
|
-
end
|
13
|
+
# -- Database Managers --
|
98
14
|
|
99
15
|
# Initialize the database manager with the current connection
|
100
16
|
def database_manager
|
@@ -106,416 +22,5 @@ module Dbviewer
|
|
106
22
|
def table_query_operations
|
107
23
|
@table_query_operations ||= database_manager.table_query_operations
|
108
24
|
end
|
109
|
-
|
110
|
-
# Get the name of the current database
|
111
|
-
def get_database_name
|
112
|
-
# First check if this connection has a name in the configuration
|
113
|
-
current_conn_config = Dbviewer.configuration.database_connections[current_connection_key]
|
114
|
-
if current_conn_config && current_conn_config[:name].present?
|
115
|
-
return current_conn_config[:name]
|
116
|
-
end
|
117
|
-
|
118
|
-
adapter = database_manager.connection.adapter_name.downcase
|
119
|
-
|
120
|
-
case adapter
|
121
|
-
when /mysql/
|
122
|
-
query = "SELECT DATABASE() as db_name"
|
123
|
-
result = database_manager.execute_query(query).first
|
124
|
-
result ? result["db_name"] : "Database"
|
125
|
-
when /postgres/
|
126
|
-
query = "SELECT current_database() as db_name"
|
127
|
-
result = database_manager.execute_query(query).first
|
128
|
-
result ? result["db_name"] : "Database"
|
129
|
-
when /sqlite/
|
130
|
-
# For SQLite, extract the database name from the connection_config
|
131
|
-
database_path = database_manager.connection.pool.spec.config[:database] || ""
|
132
|
-
File.basename(database_path, ".*") || "SQLite Database"
|
133
|
-
else
|
134
|
-
"Database" # Default fallback
|
135
|
-
end
|
136
|
-
rescue => e
|
137
|
-
Rails.logger.error("Error retrieving database name: #{e.message}")
|
138
|
-
"Database"
|
139
|
-
end
|
140
|
-
|
141
|
-
# Get the name of the current database adapter
|
142
|
-
def get_adapter_name
|
143
|
-
adapter_name = database_manager.connection.adapter_name.downcase
|
144
|
-
adapter_mappings = {
|
145
|
-
/mysql/i => "MySQL",
|
146
|
-
/postgres/i => "PostgreSQL",
|
147
|
-
/sqlite/i => "SQLite",
|
148
|
-
/oracle/i => "Oracle",
|
149
|
-
/sqlserver|mssql/i => "SQL Server"
|
150
|
-
}
|
151
|
-
adapter_mappings.find { |pattern, _| adapter_name =~ pattern }&.last || adapter_name.titleize
|
152
|
-
rescue => e
|
153
|
-
Rails.logger.error("Error retrieving adapter name: #{e.message}")
|
154
|
-
"Unknown"
|
155
|
-
end
|
156
|
-
|
157
|
-
# Fetch all tables with their stats
|
158
|
-
# By default, don't include record counts for better performance on sidebar
|
159
|
-
def fetch_tables(include_record_counts = false)
|
160
|
-
database_manager.tables.map do |table_name|
|
161
|
-
table_stats = {
|
162
|
-
name: table_name
|
163
|
-
}
|
164
|
-
|
165
|
-
# Only fetch record count if specifically requested
|
166
|
-
if include_record_counts
|
167
|
-
begin
|
168
|
-
table_stats[:record_count] = database_manager.record_count(table_name)
|
169
|
-
rescue => e
|
170
|
-
Rails.logger.error("Error fetching record count for #{table_name}: #{e.message}")
|
171
|
-
table_stats[:record_count] = 0
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
table_stats
|
176
|
-
end
|
177
|
-
rescue => e
|
178
|
-
Rails.logger.error("Error fetching tables: #{e.message}")
|
179
|
-
[]
|
180
|
-
end
|
181
|
-
|
182
|
-
# Gather database analytics information
|
183
|
-
def fetch_database_analytics
|
184
|
-
# For analytics, we do need record counts
|
185
|
-
tables = fetch_tables(include_record_counts: true)
|
186
|
-
|
187
|
-
# Calculate overall statistics
|
188
|
-
analytics = {
|
189
|
-
total_tables: tables.size,
|
190
|
-
total_records: tables.sum { |t| t[:record_count] },
|
191
|
-
largest_tables: tables.sort_by { |t| -t[:record_count] }.first(10),
|
192
|
-
empty_tables: tables.select { |t| t[:record_count] == 0 }
|
193
|
-
}
|
194
|
-
# Calculate schema size if possible
|
195
|
-
analytics[:schema_size] = calculate_schema_size
|
196
|
-
|
197
|
-
analytics
|
198
|
-
end
|
199
|
-
|
200
|
-
# Get column information for a specific table
|
201
|
-
def fetch_table_columns(table_name)
|
202
|
-
database_manager.table_columns(table_name)
|
203
|
-
end
|
204
|
-
|
205
|
-
# Get the total number of records in a table
|
206
|
-
def fetch_table_record_count(table_name)
|
207
|
-
database_manager.table_count(table_name)
|
208
|
-
end
|
209
|
-
|
210
|
-
# Fetch records for a table with pagination and sorting
|
211
|
-
def fetch_table_records(table_name, query_params)
|
212
|
-
database_manager.table_records(table_name, query_params)
|
213
|
-
end
|
214
|
-
|
215
|
-
# Get filtered record count for a table
|
216
|
-
def fetch_filtered_record_count(table_name, column_filters)
|
217
|
-
database_manager.filtered_record_count(table_name, column_filters)
|
218
|
-
end
|
219
|
-
|
220
|
-
# Safely quote a table name, with fallback
|
221
|
-
def safe_quote_table_name(table_name)
|
222
|
-
database_manager.connection.quote_table_name(table_name)
|
223
|
-
rescue => e
|
224
|
-
Rails.logger.warn("Failed to quote table name: #{e.message}")
|
225
|
-
table_name
|
226
|
-
end
|
227
|
-
|
228
|
-
# Get table metadata for display (e.g., primary key, foreign keys, indexes)
|
229
|
-
def fetch_table_metadata(table_name)
|
230
|
-
return {} unless database_manager.respond_to?(:table_metadata)
|
231
|
-
|
232
|
-
begin
|
233
|
-
database_manager.table_metadata(table_name)
|
234
|
-
rescue => e
|
235
|
-
Rails.logger.warn("Failed to fetch table metadata: #{e.message}")
|
236
|
-
{}
|
237
|
-
end
|
238
|
-
end
|
239
|
-
|
240
|
-
# Fetch relationships between tables for ERD visualization
|
241
|
-
def fetch_table_relationships
|
242
|
-
# Use functional approach: flat_map to extract all relationships from all tables
|
243
|
-
@tables.flat_map do |table|
|
244
|
-
extract_table_relationships_from_metadata(table[:name])
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
private
|
249
|
-
|
250
|
-
# Extract relationships for a single table from its metadata
|
251
|
-
# @param table_name [String] The name of the table to process
|
252
|
-
# @return [Array<Hash>] Array of relationship hashes for this table
|
253
|
-
def extract_table_relationships_from_metadata(table_name)
|
254
|
-
metadata = database_manager.table_metadata(table_name)
|
255
|
-
return [] unless metadata&.dig(:foreign_keys)&.present?
|
256
|
-
|
257
|
-
metadata[:foreign_keys].map do |fk|
|
258
|
-
{
|
259
|
-
from_table: table_name,
|
260
|
-
to_table: fk[:to_table],
|
261
|
-
from_column: fk[:column],
|
262
|
-
to_column: fk[:primary_key],
|
263
|
-
name: fk[:name]
|
264
|
-
}
|
265
|
-
end
|
266
|
-
rescue => e
|
267
|
-
Rails.logger.error("Error fetching relationships for #{table_name}: #{e.message}")
|
268
|
-
[] # Return empty array to continue processing other tables
|
269
|
-
end
|
270
|
-
|
271
|
-
# Get mini ERD data for a specific table and its relationships
|
272
|
-
def fetch_mini_erd_for_table(table_name)
|
273
|
-
related_tables = []
|
274
|
-
relationships = []
|
275
|
-
|
276
|
-
# Validate the table exists
|
277
|
-
unless database_manager.tables.include?(table_name)
|
278
|
-
Rails.logger.error("[DBViewer] Table not found for mini ERD: #{table_name}")
|
279
|
-
return {
|
280
|
-
tables: [],
|
281
|
-
relationships: [],
|
282
|
-
error: "Table '#{table_name}' not found in the database"
|
283
|
-
}
|
284
|
-
end
|
285
|
-
|
286
|
-
# Add current table
|
287
|
-
related_tables << { name: table_name }
|
288
|
-
|
289
|
-
Rails.logger.info("[DBViewer] Generating mini ERD for table: #{table_name}")
|
290
|
-
|
291
|
-
# Get foreign keys from this table to others (outgoing relationships)
|
292
|
-
begin
|
293
|
-
metadata = fetch_table_metadata(table_name)
|
294
|
-
Rails.logger.debug("[DBViewer] Table metadata: #{metadata.inspect}")
|
295
|
-
|
296
|
-
if metadata && metadata[:foreign_keys].present?
|
297
|
-
metadata[:foreign_keys].each do |fk|
|
298
|
-
# Ensure all required fields are present
|
299
|
-
next unless fk[:to_table].present? && fk[:column].present?
|
300
|
-
|
301
|
-
# Sanitize table and column names for display
|
302
|
-
from_table = table_name.to_s
|
303
|
-
to_table = fk[:to_table].to_s
|
304
|
-
from_column = fk[:column].to_s
|
305
|
-
to_column = fk[:primary_key].to_s.presence || "id"
|
306
|
-
relationship_name = fk[:name].to_s.presence || "#{from_table}_to_#{to_table}"
|
307
|
-
|
308
|
-
relationship = {
|
309
|
-
from_table: from_table,
|
310
|
-
to_table: to_table,
|
311
|
-
from_column: from_column,
|
312
|
-
to_column: to_column,
|
313
|
-
name: relationship_name,
|
314
|
-
direction: "outgoing"
|
315
|
-
}
|
316
|
-
|
317
|
-
relationships << relationship
|
318
|
-
Rails.logger.debug("[DBViewer] Added outgoing relationship: #{relationship.inspect}")
|
319
|
-
|
320
|
-
# Add the related table if not already included
|
321
|
-
unless related_tables.any? { |t| t[:name] == to_table }
|
322
|
-
related_tables << { name: to_table }
|
323
|
-
end
|
324
|
-
end
|
325
|
-
end
|
326
|
-
rescue => e
|
327
|
-
Rails.logger.error("[DBViewer] Error fetching outgoing relationships for #{table_name}: #{e.message}")
|
328
|
-
Rails.logger.error(e.backtrace.join("\n"))
|
329
|
-
end
|
330
|
-
|
331
|
-
# Get foreign keys from other tables to this one (incoming relationships)
|
332
|
-
begin
|
333
|
-
database_manager.tables.each do |other_table_name|
|
334
|
-
next if other_table_name == table_name # Skip self
|
335
|
-
|
336
|
-
begin
|
337
|
-
other_metadata = fetch_table_metadata(other_table_name)
|
338
|
-
if other_metadata && other_metadata[:foreign_keys].present?
|
339
|
-
other_metadata[:foreign_keys].each do |fk|
|
340
|
-
if fk[:to_table] == table_name
|
341
|
-
# Ensure all required fields are present
|
342
|
-
next unless fk[:column].present?
|
343
|
-
|
344
|
-
# Sanitize table and column names for display
|
345
|
-
from_table = other_table_name.to_s
|
346
|
-
to_table = table_name.to_s
|
347
|
-
from_column = fk[:column].to_s
|
348
|
-
to_column = fk[:primary_key].to_s.presence || "id"
|
349
|
-
relationship_name = fk[:name].to_s.presence || "#{from_table}_to_#{to_table}"
|
350
|
-
|
351
|
-
relationship = {
|
352
|
-
from_table: from_table,
|
353
|
-
to_table: to_table,
|
354
|
-
from_column: from_column,
|
355
|
-
to_column: to_column,
|
356
|
-
name: relationship_name,
|
357
|
-
direction: "incoming"
|
358
|
-
}
|
359
|
-
|
360
|
-
relationships << relationship
|
361
|
-
Rails.logger.debug("[DBViewer] Added incoming relationship: #{relationship.inspect}")
|
362
|
-
|
363
|
-
# Add the related table if not already included
|
364
|
-
unless related_tables.any? { |t| t[:name] == from_table }
|
365
|
-
related_tables << { name: from_table }
|
366
|
-
end
|
367
|
-
end
|
368
|
-
end
|
369
|
-
end
|
370
|
-
rescue => e
|
371
|
-
Rails.logger.error("[DBViewer] Error processing relationships for table #{other_table_name}: #{e.message}")
|
372
|
-
# Continue to the next table
|
373
|
-
end
|
374
|
-
end
|
375
|
-
rescue => e
|
376
|
-
Rails.logger.error("[DBViewer] Error fetching incoming relationships for #{table_name}: #{e.message}")
|
377
|
-
Rails.logger.error(e.backtrace.join("\n"))
|
378
|
-
end
|
379
|
-
|
380
|
-
# If no relationships were found, make sure to still include at least the current table
|
381
|
-
if relationships.empty?
|
382
|
-
Rails.logger.info("[DBViewer] No relationships found for table: #{table_name}")
|
383
|
-
end
|
384
|
-
|
385
|
-
result = {
|
386
|
-
tables: related_tables,
|
387
|
-
relationships: relationships,
|
388
|
-
timestamp: Time.now.to_i
|
389
|
-
}
|
390
|
-
|
391
|
-
Rails.logger.info("[DBViewer] Mini ERD data generated: #{related_tables.length} tables, #{relationships.length} relationships")
|
392
|
-
result
|
393
|
-
end
|
394
|
-
|
395
|
-
# Prepare the SQL query - either from params or default
|
396
|
-
def prepare_query
|
397
|
-
quoted_table = safe_quote_table_name(@table_name)
|
398
|
-
default_query = "SELECT * FROM #{quoted_table} LIMIT 100"
|
399
|
-
|
400
|
-
# Use the raw query parameter, or fall back to default
|
401
|
-
@query = params[:query].present? ? params[:query].to_s : default_query
|
402
|
-
|
403
|
-
# Validate query for security
|
404
|
-
unless ::Dbviewer::Validator::Sql.safe_query?(@query)
|
405
|
-
@query = default_query
|
406
|
-
flash.now[:warning] = "Only SELECT queries are allowed. Your query contained potentially unsafe operations. Using default query instead."
|
407
|
-
end
|
408
|
-
end
|
409
|
-
|
410
|
-
# Execute the prepared SQL query
|
411
|
-
def execute_query
|
412
|
-
begin
|
413
|
-
@records = database_manager.execute_query(@query)
|
414
|
-
@error = nil
|
415
|
-
rescue => e
|
416
|
-
@records = nil
|
417
|
-
@error = e.message
|
418
|
-
Rails.logger.error("SQL Query Error: #{e.message} for query: #{@query}")
|
419
|
-
end
|
420
|
-
end
|
421
|
-
|
422
|
-
# Helper to check if this is the current table in the UI
|
423
|
-
def current_table?(table_name)
|
424
|
-
params[:id] == table_name
|
425
|
-
end
|
426
|
-
|
427
|
-
# Export table data to CSV
|
428
|
-
def export_table_to_csv(table_name, query_params = nil, include_headers = true)
|
429
|
-
records = database_manager.table_query_operations.table_records(table_name, query_params)
|
430
|
-
|
431
|
-
csv_data = CSV.generate do |csv|
|
432
|
-
# Add headers if requested
|
433
|
-
csv << records.columns if include_headers
|
434
|
-
|
435
|
-
# Add rows
|
436
|
-
records.rows.each do |row|
|
437
|
-
csv << row.map { |cell| format_csv_value(cell) }
|
438
|
-
end
|
439
|
-
end
|
440
|
-
|
441
|
-
csv_data
|
442
|
-
end
|
443
|
-
|
444
|
-
# Consolidated method to fetch all datatable-related data in one call
|
445
|
-
# Returns a hash containing all necessary datatable information
|
446
|
-
def fetch_datatable_data(table_name, query_params)
|
447
|
-
# Fetch all required data using functional programming patterns
|
448
|
-
columns = fetch_table_columns(table_name)
|
449
|
-
|
450
|
-
# Handle case where table has no columns (should rarely happen)
|
451
|
-
return default_datatable_structure(table_name) if columns.empty?
|
452
|
-
|
453
|
-
# Fetch records with error handling for null cases
|
454
|
-
records = begin
|
455
|
-
fetch_table_records(table_name, query_params)
|
456
|
-
rescue => e
|
457
|
-
Rails.logger.error("Error fetching table records for #{table_name}: #{e.message}")
|
458
|
-
nil
|
459
|
-
end
|
460
|
-
|
461
|
-
# Calculate total count - use filtered count if filters are present
|
462
|
-
total_count = begin
|
463
|
-
if query_params.column_filters.empty?
|
464
|
-
fetch_table_record_count(table_name)
|
465
|
-
else
|
466
|
-
fetch_filtered_record_count(table_name, query_params.column_filters)
|
467
|
-
end
|
468
|
-
rescue => e
|
469
|
-
Rails.logger.error("Error fetching record count for #{table_name}: #{e.message}")
|
470
|
-
0
|
471
|
-
end
|
472
|
-
|
473
|
-
# Calculate pagination data functionally with safety check
|
474
|
-
total_pages = total_count > 0 ? (total_count.to_f / query_params.per_page).ceil : 0
|
475
|
-
|
476
|
-
# Get metadata with error handling
|
477
|
-
metadata = fetch_table_metadata(table_name)
|
478
|
-
|
479
|
-
{
|
480
|
-
columns: columns,
|
481
|
-
records: records,
|
482
|
-
total_count: total_count,
|
483
|
-
total_pages: total_pages,
|
484
|
-
metadata: metadata,
|
485
|
-
current_page: query_params.page,
|
486
|
-
per_page: query_params.per_page,
|
487
|
-
order_by: query_params.order_by,
|
488
|
-
direction: query_params.direction
|
489
|
-
}
|
490
|
-
end
|
491
|
-
|
492
|
-
private
|
493
|
-
|
494
|
-
# Default structure for tables with no data/columns
|
495
|
-
def default_datatable_structure(table_name)
|
496
|
-
{
|
497
|
-
columns: [],
|
498
|
-
records: [],
|
499
|
-
total_count: 0,
|
500
|
-
total_pages: 0,
|
501
|
-
metadata: {},
|
502
|
-
current_page: 1,
|
503
|
-
per_page: 25,
|
504
|
-
order_by: nil,
|
505
|
-
direction: "ASC"
|
506
|
-
}
|
507
|
-
end
|
508
|
-
|
509
|
-
# Format cell values for CSV export to handle nil values and special characters
|
510
|
-
def format_csv_value(value)
|
511
|
-
return "" if value.nil?
|
512
|
-
value.to_s
|
513
|
-
end
|
514
|
-
|
515
|
-
# Check if a table has a created_at column for timestamp visualization
|
516
|
-
def has_timestamp_column?(table_name)
|
517
|
-
columns = fetch_table_columns(table_name)
|
518
|
-
columns.any? { |col| col[:name] == "created_at" && [ :datetime, :timestamp ].include?(col[:type]) }
|
519
|
-
end
|
520
25
|
end
|
521
26
|
end
|
@@ -0,0 +1,47 @@
|
|
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
|
@@ -0,0 +1,28 @@
|
|
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
|