Class: RailsLens::Schema::Adapters::Mysql

Inherits:
Base
  • Object
show all
Defined in:
lib/rails_lens/schema/adapters/mysql.rb

Instance Attribute Summary

Attributes inherited from Base

#connection, #table_name

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

#generate_view_annotation, #initialize, #unqualified_table_name

Constructor Details

This class inherits a constructor from RailsLens::Schema::Adapters::Base

Class Method Details

.fetch_functions(connection) ⇒ Object

Fetch all user-defined functions



383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
# File 'lib/rails_lens/schema/adapters/mysql.rb', line 383

def self.fetch_functions(connection)
  result = connection.exec_query(<<~SQL.squish, 'MySQL Functions')
    SELECT
      ROUTINE_NAME AS name,
      ROUTINE_SCHEMA AS `schema`,
      DATA_TYPE AS return_type,
      ROUTINE_COMMENT AS description
    FROM information_schema.ROUTINES
    WHERE ROUTINE_SCHEMA = DATABASE()
      AND ROUTINE_TYPE = 'FUNCTION'
    ORDER BY ROUTINE_NAME
  SQL

  result.rows.map do |row|
    {
      name: row[0],
      schema: row[1],
      language: 'SQL',
      return_type: row[2],
      description: row[3]
    }
  end
rescue ActiveRecord::StatementInvalid, Mysql2::Error => e
  RailsLens.logger.debug { "Failed to fetch functions: #{e.message}" }
  []
end

Instance Method Details

#adapter_nameObject



7
8
9
# File 'lib/rails_lens/schema/adapters/mysql.rb', line 7

def adapter_name
  'MySQL'
end

#extract_function_call(action_statement) ⇒ Object

Extract function/procedure call from trigger action statement



368
369
370
371
372
373
374
375
376
377
378
379
380
# File 'lib/rails_lens/schema/adapters/mysql.rb', line 368

def extract_function_call(action_statement)
  return nil unless action_statement

  # Match CALL with optional schema prefix and backticks: CALL `schema`.`proc` or CALL schema.proc or CALL proc
  match = action_statement.match(/CALL\s+(?:`?(\w+)`?\.)?`?(\w+)`?/i)
  if match
    schema_part = match[1]
    proc_name = match[2]
    schema_part ? "#{schema_part}.#{proc_name}" : proc_name
  else
    'inline'
  end
end

#fetch_triggersObject

Fetch triggers for the table



337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/rails_lens/schema/adapters/mysql.rb', line 337

def fetch_triggers
  quoted_table = connection.quote(unqualified_table_name)
  result = connection.exec_query(<<~SQL.squish, 'MySQL Table Triggers')
    SELECT
      TRIGGER_NAME AS name,
      ACTION_TIMING AS timing,
      EVENT_MANIPULATION AS event,
      'ROW' AS for_each,
      ACTION_STATEMENT AS action_statement
    FROM information_schema.TRIGGERS
    WHERE TRIGGER_SCHEMA = DATABASE()
      AND EVENT_OBJECT_TABLE = #{quoted_table}
    ORDER BY TRIGGER_NAME
  SQL

  result.rows.map do |row|
    {
      name: row[0],
      timing: row[1],
      event: row[2],
      for_each: row[3],
      function: extract_function_call(row[4]),
      definition: row[4]
    }
  end
rescue ActiveRecord::StatementInvalid, Mysql2::Error => e
  RailsLens.logger.debug { "Failed to fetch triggers for #{table_name}: #{e.message}" }
  []
end

#fetch_view_metadataObject

Fetch all view metadata in a single consolidated query



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/rails_lens/schema/adapters/mysql.rb', line 267

def 
  result = connection.exec_query(<<~SQL.squish, 'MySQL View Metadata')
    SELECT
      v.is_updatable,
      COALESCE(
        (
          SELECT GROUP_CONCAT(DISTINCT vtu.table_name ORDER BY vtu.table_name)
          FROM information_schema.view_table_usage vtu
          WHERE vtu.view_schema = DATABASE()
          AND vtu.view_name = '#{connection.quote_string(table_name)}'
        ),
        ''
      ) as dependencies
    FROM information_schema.views v
    WHERE v.table_schema = DATABASE()
    AND v.table_name = '#{connection.quote_string(table_name)}'
    LIMIT 1
  SQL

  return nil if result.rows.empty?

  row = result.rows.first
  {
    type: 'regular', # MySQL only supports regular views
    updatable: row[0] == 'YES',
    dependencies: row[1].to_s.split(',').reject(&:empty?)
  }
rescue ActiveRecord::StatementInvalid, Mysql2::Error => e
  RailsLens.logger.debug { "Failed to fetch view metadata for #{table_name}: #{e.message}" }
  nil
end

#generate_annotation(model_class) ⇒ Object



11
12
13
14
15
16
17
# File 'lib/rails_lens/schema/adapters/mysql.rb', line 11

def generate_annotation(model_class)
  if model_class && ModelDetector.view_exists?(model_class)
    generate_view_annotation(model_class)
  else
    generate_table_annotation(model_class)
  end
end

#generate_table_annotation(_model_class) ⇒ Object



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/rails_lens/schema/adapters/mysql.rb', line 19

def generate_table_annotation(_model_class)
  lines = []
  lines << "table = \"#{table_name}\""
  lines << "database_dialect = \"#{database_dialect}\""

  # Add storage engine information
  if (engine = table_storage_engine)
    lines << "storage_engine = \"#{engine}\""
  end

  # Add character set and collation
  if (charset = table_charset)
    lines << "character_set = \"#{charset}\""
  end

  if (collation = table_collation)
    lines << "collation = \"#{collation}\""
  end

  lines << ''

  add_columns_toml(lines)
  add_indexes_toml(lines) if show_indexes?
  add_foreign_keys_toml(lines) if show_foreign_keys?
  add_triggers_toml(lines) if show_triggers?
  add_partitions_toml(lines) if has_partitions?

  lines.join("\n")
end

#view_definitionObject



315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/rails_lens/schema/adapters/mysql.rb', line 315

def view_definition
  result = connection.exec_query(<<~SQL.squish, 'MySQL View Definition')
    SELECT view_definition FROM information_schema.views
    WHERE table_schema = DATABASE()
    AND table_name = '#{connection.quote_string(table_name)}'
    LIMIT 1
  SQL

  result.rows.first&.first&.strip
rescue ActiveRecord::StatementInvalid, Mysql2::Error
  nil
end

#view_dependenciesObject



310
311
312
313
# File 'lib/rails_lens/schema/adapters/mysql.rb', line 310

def view_dependencies
  @view_metadata ||= 
  @view_metadata&.dig(:dependencies) || []
end

#view_last_refreshedObject



332
333
334
# File 'lib/rails_lens/schema/adapters/mysql.rb', line 332

def view_last_refreshed
  nil # MySQL doesn't have materialized views
end

#view_refresh_strategyObject



328
329
330
# File 'lib/rails_lens/schema/adapters/mysql.rb', line 328

def view_refresh_strategy
  nil # MySQL doesn't have materialized views
end

#view_typeObject

Legacy methods - kept for backward compatibility but now use consolidated query



300
301
302
303
# File 'lib/rails_lens/schema/adapters/mysql.rb', line 300

def view_type
  @view_metadata ||= 
  @view_metadata&.dig(:type)
end

#view_updatable?Boolean

Returns:

  • (Boolean)


305
306
307
308
# File 'lib/rails_lens/schema/adapters/mysql.rb', line 305

def view_updatable?
  @view_metadata ||= 
  @view_metadata&.dig(:updatable) || false
end