dbviewer 0.6.2 → 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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +43 -14
  3. data/app/controllers/concerns/dbviewer/connection_management.rb +88 -0
  4. data/app/controllers/concerns/dbviewer/data_export.rb +32 -0
  5. data/app/controllers/concerns/dbviewer/database_information.rb +62 -0
  6. data/app/controllers/concerns/dbviewer/database_operations.rb +8 -514
  7. data/app/controllers/concerns/dbviewer/datatable_support.rb +47 -0
  8. data/app/controllers/concerns/dbviewer/query_operations.rb +28 -0
  9. data/app/controllers/concerns/dbviewer/relationship_management.rb +173 -0
  10. data/app/controllers/concerns/dbviewer/table_operations.rb +56 -0
  11. data/app/controllers/dbviewer/api/entity_relationship_diagrams_controller.rb +26 -24
  12. data/app/controllers/dbviewer/tables_controller.rb +16 -11
  13. data/app/helpers/dbviewer/application_helper.rb +9 -521
  14. data/app/helpers/dbviewer/database_helper.rb +59 -0
  15. data/app/helpers/dbviewer/filter_helper.rb +137 -0
  16. data/app/helpers/dbviewer/formatting_helper.rb +30 -0
  17. data/app/helpers/dbviewer/navigation_helper.rb +35 -0
  18. data/app/helpers/dbviewer/pagination_helper.rb +72 -0
  19. data/app/helpers/dbviewer/sorting_helper.rb +47 -0
  20. data/app/helpers/dbviewer/table_rendering_helper.rb +145 -0
  21. data/app/helpers/dbviewer/ui_helper.rb +41 -0
  22. data/app/views/dbviewer/tables/show.html.erb +225 -139
  23. data/app/views/layouts/dbviewer/application.html.erb +55 -0
  24. data/lib/dbviewer/database/dynamic_model_factory.rb +40 -5
  25. data/lib/dbviewer/database/manager.rb +2 -3
  26. data/lib/dbviewer/datatable/query_operations.rb +84 -214
  27. data/lib/dbviewer/engine.rb +1 -22
  28. data/lib/dbviewer/query/executor.rb +1 -36
  29. data/lib/dbviewer/query/notification_subscriber.rb +46 -0
  30. data/lib/dbviewer/validator/sql.rb +198 -0
  31. data/lib/dbviewer/validator.rb +9 -0
  32. data/lib/dbviewer/version.rb +1 -1
  33. data/lib/dbviewer.rb +69 -45
  34. data/lib/generators/dbviewer/templates/initializer.rb +15 -0
  35. metadata +20 -3
  36. data/lib/dbviewer/sql_validator.rb +0 -194
@@ -1,100 +1,16 @@
1
- require "csv"
2
-
3
1
  module Dbviewer
4
2
  module DatabaseOperations
5
3
  extend ActiveSupport::Concern
6
4
 
7
- included do
8
- helper_method :current_table?, :get_database_name, :get_adapter_name,
9
- :current_connection_key, :available_connections if respond_to?(:helper_method)
10
- end
11
-
12
- # Get the current active connection key
13
- def current_connection_key
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,427 +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
- relationships = []
243
-
244
- @tables.each do |table|
245
- table_name = table[:name]
246
-
247
- # Get foreign keys defined in this table pointing to others
248
- begin
249
- metadata = database_manager.table_metadata(table_name)
250
- if metadata && metadata[:foreign_keys].present?
251
- metadata[:foreign_keys].each do |fk|
252
- relationships << {
253
- from_table: table_name,
254
- to_table: fk[:to_table],
255
- from_column: fk[:column],
256
- to_column: fk[:primary_key],
257
- name: fk[:name]
258
- }
259
- end
260
- end
261
- rescue => e
262
- Rails.logger.error("Error fetching relationships for #{table_name}: #{e.message}")
263
- end
264
- end
265
-
266
- relationships
267
- end
268
-
269
- # Get mini ERD data for a specific table and its relationships
270
- def fetch_mini_erd_for_table(table_name)
271
- related_tables = []
272
- relationships = []
273
-
274
- # Validate the table exists
275
- unless database_manager.tables.include?(table_name)
276
- Rails.logger.error("[DBViewer] Table not found for mini ERD: #{table_name}")
277
- return {
278
- tables: [],
279
- relationships: [],
280
- error: "Table '#{table_name}' not found in the database"
281
- }
282
- end
283
-
284
- # Add current table
285
- related_tables << { name: table_name }
286
-
287
- Rails.logger.info("[DBViewer] Generating mini ERD for table: #{table_name}")
288
-
289
- # Get foreign keys from this table to others (outgoing relationships)
290
- begin
291
- metadata = fetch_table_metadata(table_name)
292
- Rails.logger.debug("[DBViewer] Table metadata: #{metadata.inspect}")
293
-
294
- if metadata && metadata[:foreign_keys].present?
295
- metadata[:foreign_keys].each do |fk|
296
- # Ensure all required fields are present
297
- next unless fk[:to_table].present? && fk[:column].present?
298
-
299
- # Sanitize table and column names for display
300
- from_table = table_name.to_s
301
- to_table = fk[:to_table].to_s
302
- from_column = fk[:column].to_s
303
- to_column = fk[:primary_key].to_s.presence || "id"
304
- relationship_name = fk[:name].to_s.presence || "#{from_table}_to_#{to_table}"
305
-
306
- relationship = {
307
- from_table: from_table,
308
- to_table: to_table,
309
- from_column: from_column,
310
- to_column: to_column,
311
- name: relationship_name,
312
- direction: "outgoing"
313
- }
314
-
315
- relationships << relationship
316
- Rails.logger.debug("[DBViewer] Added outgoing relationship: #{relationship.inspect}")
317
-
318
- # Add the related table if not already included
319
- unless related_tables.any? { |t| t[:name] == to_table }
320
- related_tables << { name: to_table }
321
- end
322
- end
323
- end
324
- rescue => e
325
- Rails.logger.error("[DBViewer] Error fetching outgoing relationships for #{table_name}: #{e.message}")
326
- Rails.logger.error(e.backtrace.join("\n"))
327
- end
328
-
329
- # Get foreign keys from other tables to this one (incoming relationships)
330
- begin
331
- database_manager.tables.each do |other_table_name|
332
- next if other_table_name == table_name # Skip self
333
-
334
- begin
335
- other_metadata = fetch_table_metadata(other_table_name)
336
- if other_metadata && other_metadata[:foreign_keys].present?
337
- other_metadata[:foreign_keys].each do |fk|
338
- if fk[:to_table] == table_name
339
- # Ensure all required fields are present
340
- next unless fk[:column].present?
341
-
342
- # Sanitize table and column names for display
343
- from_table = other_table_name.to_s
344
- to_table = table_name.to_s
345
- from_column = fk[:column].to_s
346
- to_column = fk[:primary_key].to_s.presence || "id"
347
- relationship_name = fk[:name].to_s.presence || "#{from_table}_to_#{to_table}"
348
-
349
- relationship = {
350
- from_table: from_table,
351
- to_table: to_table,
352
- from_column: from_column,
353
- to_column: to_column,
354
- name: relationship_name,
355
- direction: "incoming"
356
- }
357
-
358
- relationships << relationship
359
- Rails.logger.debug("[DBViewer] Added incoming relationship: #{relationship.inspect}")
360
-
361
- # Add the related table if not already included
362
- unless related_tables.any? { |t| t[:name] == from_table }
363
- related_tables << { name: from_table }
364
- end
365
- end
366
- end
367
- end
368
- rescue => e
369
- Rails.logger.error("[DBViewer] Error processing relationships for table #{other_table_name}: #{e.message}")
370
- # Continue to the next table
371
- end
372
- end
373
- rescue => e
374
- Rails.logger.error("[DBViewer] Error fetching incoming relationships for #{table_name}: #{e.message}")
375
- Rails.logger.error(e.backtrace.join("\n"))
376
- end
377
-
378
- # If no relationships were found, make sure to still include at least the current table
379
- if relationships.empty?
380
- Rails.logger.info("[DBViewer] No relationships found for table: #{table_name}")
381
- end
382
-
383
- result = {
384
- tables: related_tables,
385
- relationships: relationships,
386
- timestamp: Time.now.to_i
387
- }
388
-
389
- Rails.logger.info("[DBViewer] Mini ERD data generated: #{related_tables.length} tables, #{relationships.length} relationships")
390
- result
391
- end
392
-
393
- # Prepare the SQL query - either from params or default
394
- def prepare_query
395
- quoted_table = safe_quote_table_name(@table_name)
396
- default_query = "SELECT * FROM #{quoted_table} LIMIT 100"
397
-
398
- # Use the raw query parameter, or fall back to default
399
- @query = params[:query].present? ? params[:query].to_s : default_query
400
-
401
- # Validate query for security
402
- unless ::Dbviewer::SqlValidator.safe_query?(@query)
403
- @query = default_query
404
- flash.now[:warning] = "Only SELECT queries are allowed. Your query contained potentially unsafe operations. Using default query instead."
405
- end
406
- end
407
-
408
- # Execute the prepared SQL query
409
- def execute_query
410
- begin
411
- @records = database_manager.execute_query(@query)
412
- @error = nil
413
- rescue => e
414
- @records = nil
415
- @error = e.message
416
- Rails.logger.error("SQL Query Error: #{e.message} for query: #{@query}")
417
- end
418
- end
419
-
420
- # Helper to check if this is the current table in the UI
421
- def current_table?(table_name)
422
- params[:id] == table_name
423
- end
424
-
425
- # Export table data to CSV
426
- def export_table_to_csv(table_name, query_params = nil, include_headers = true)
427
- records = database_manager.table_query_operations.table_records(table_name, query_params)
428
-
429
- csv_data = CSV.generate do |csv|
430
- # Add headers if requested
431
- csv << records.columns if include_headers
432
-
433
- # Add rows
434
- records.rows.each do |row|
435
- csv << row.map { |cell| format_csv_value(cell) }
436
- end
437
- end
438
-
439
- csv_data
440
- end
441
-
442
- private
443
-
444
- # Format cell values for CSV export to handle nil values and special characters
445
- def format_csv_value(value)
446
- return "" if value.nil?
447
- value.to_s
448
- end
449
-
450
- # Check if a table has a created_at column for timestamp visualization
451
- def has_timestamp_column?(table_name)
452
- columns = fetch_table_columns(table_name)
453
- columns.any? { |col| col[:name] == "created_at" && [ :datetime, :timestamp ].include?(col[:type]) }
454
- end
455
-
456
- # Fetch timestamp data for visualization (hourly, daily, weekly)
457
- def fetch_timestamp_data(table_name, grouping = "daily")
458
- return nil unless has_timestamp_column?(table_name)
459
-
460
- quoted_table = safe_quote_table_name(table_name)
461
- adapter = database_manager.connection.adapter_name.downcase
462
-
463
- sql_query = case grouping
464
- when "hourly"
465
- case adapter
466
- when /mysql/
467
- "SELECT DATE_FORMAT(created_at, '%Y-%m-%d %H:00:00') as time_group, COUNT(*) as count FROM #{quoted_table} GROUP BY time_group ORDER BY time_group DESC LIMIT 48"
468
- when /postgres/
469
- "SELECT date_trunc('hour', created_at) as time_group, COUNT(*) as count FROM #{quoted_table} GROUP BY time_group ORDER BY time_group DESC LIMIT 48"
470
- else # SQLite and others
471
- "SELECT strftime('%Y-%m-%d %H:00:00', created_at) as time_group, COUNT(*) as count FROM #{quoted_table} GROUP BY time_group ORDER BY time_group DESC LIMIT 48"
472
- end
473
- when "daily"
474
- case adapter
475
- when /mysql/
476
- "SELECT DATE_FORMAT(created_at, '%Y-%m-%d') as time_group, COUNT(*) as count FROM #{quoted_table} GROUP BY time_group ORDER BY time_group DESC LIMIT 30"
477
- when /postgres/
478
- "SELECT date_trunc('day', created_at) as time_group, COUNT(*) as count FROM #{quoted_table} GROUP BY time_group ORDER BY time_group DESC LIMIT 30"
479
- else # SQLite and others
480
- "SELECT strftime('%Y-%m-%d', created_at) as time_group, COUNT(*) as count FROM #{quoted_table} GROUP BY time_group ORDER BY time_group DESC LIMIT 30"
481
- end
482
- when "weekly"
483
- case adapter
484
- when /mysql/
485
- "SELECT DATE_FORMAT(created_at, '%Y-%u') as time_group, YEARWEEK(created_at) as sort_key, COUNT(*) as count FROM #{quoted_table} GROUP BY time_group ORDER BY sort_key DESC LIMIT 26"
486
- when /postgres/
487
- "SELECT date_trunc('week', created_at) as time_group, COUNT(*) as count FROM #{quoted_table} GROUP BY time_group ORDER BY time_group DESC LIMIT 26"
488
- else # SQLite and others
489
- "SELECT strftime('%Y-%W', created_at) as time_group, COUNT(*) as count FROM #{quoted_table} GROUP BY time_group ORDER BY time_group DESC LIMIT 26"
490
- end
491
- else
492
- return nil
493
- end
494
-
495
- begin
496
- result = database_manager.execute_query(sql_query)
497
-
498
- # Format the data for the chart
499
- result.map do |row|
500
- time_str = row["time_group"].to_s
501
- count = row["count"].to_i
502
-
503
- # Format the label based on grouping type
504
- label = case grouping
505
- when "hourly"
506
- # For hourly, show "May 10, 2PM"
507
- time = time_str.is_a?(Time) ? time_str : Time.parse(time_str)
508
- time.strftime("%b %d, %l%p")
509
- when "daily"
510
- # For daily, show "May 10"
511
- time = time_str.is_a?(Time) ? time_str : (time_str.include?("-") ? Time.parse(time_str) : Time.now)
512
- time.strftime("%b %d")
513
- when "weekly"
514
- # For weekly, show "Week 19" or the week's start date
515
- if time_str.include?("-")
516
- week_num = time_str.split("-").last.to_i
517
- "Week #{week_num}"
518
- else
519
- time = time_str.is_a?(Time) ? time_str : Time.parse(time_str)
520
- "Week #{time.strftime('%W')}"
521
- end
522
- end
523
-
524
- { label: label, value: count, raw_date: time_str }
525
- end
526
- rescue => e
527
- Rails.logger.error("[DBViewer] Error fetching timestamp data: #{e.message}")
528
- nil
529
- end
530
- end
531
25
  end
532
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