170 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Ruby
		
	
	
	
			
		
		
	
	
			170 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Ruby
		
	
	
	
| # frozen_string_literal: true
 | |
| 
 | |
| module Gitlab
 | |
|   module Schema
 | |
|     module Validation
 | |
|       module Sources
 | |
|         class StructureSql
 | |
|           DEFAULT_SCHEMA = 'public'
 | |
| 
 | |
|           def initialize(structure_file_path, schema_name = DEFAULT_SCHEMA)
 | |
|             @structure_file_path = structure_file_path
 | |
|             @schema_name = schema_name
 | |
|             @table_map = to_map(tables)
 | |
|             @index_map = to_map(indexes)
 | |
|             @trigger_map = to_map(triggers)
 | |
|             @foreign_key_map = to_map(foreign_keys)
 | |
|           end
 | |
| 
 | |
|           def fetch_index_by_name(index_name)
 | |
|             index_map[index_name]
 | |
|           end
 | |
| 
 | |
|           def fetch_trigger_by_name(trigger_name)
 | |
|             trigger_map[trigger_name]
 | |
|           end
 | |
| 
 | |
|           def fetch_foreign_key_by_name(foreign_key_name)
 | |
|             foreign_key_map[foreign_key_name]
 | |
|           end
 | |
| 
 | |
|           def fetch_table_by_name(table_name)
 | |
|             table_map[table_name]
 | |
|           end
 | |
| 
 | |
|           def fetch_sequence_by_name(sequence_name)
 | |
|             sequence_map[sequence_name]
 | |
|           end
 | |
| 
 | |
|           def index_exists?(index_name)
 | |
|             !!fetch_index_by_name(index_name)
 | |
|           end
 | |
| 
 | |
|           def trigger_exists?(trigger_name)
 | |
|             !!fetch_trigger_by_name(trigger_name)
 | |
|           end
 | |
| 
 | |
|           def foreign_key_exists?(foreign_key_name)
 | |
|             !!fetch_foreign_key_by_name(foreign_key_name)
 | |
|           end
 | |
| 
 | |
|           def table_exists?(table_name)
 | |
|             !!fetch_table_by_name(table_name)
 | |
|           end
 | |
| 
 | |
|           def sequence_exists?(sequence_name)
 | |
|             !!fetch_sequence_by_name(sequence_name)
 | |
|           end
 | |
| 
 | |
|           def indexes
 | |
|             @indexes ||= map_with_default_schema(index_statements, SchemaObjects::Index)
 | |
|           end
 | |
| 
 | |
|           def triggers
 | |
|             @triggers ||= map_with_default_schema(trigger_statements, SchemaObjects::Trigger)
 | |
|           end
 | |
| 
 | |
|           def sequences
 | |
|             sequence_map.values
 | |
|           end
 | |
| 
 | |
|           def sequence_map
 | |
|             @sequences ||= begin
 | |
|               parser = Gitlab::Schema::Validation::Sources::SequenceStructureSqlParser.new(
 | |
|                 parsed_structure_file, schema_name)
 | |
|               parser.execute.transform_values! { |sequence| SchemaObjects::Sequence.new(sequence) }
 | |
|             end
 | |
|           end
 | |
| 
 | |
|           def foreign_keys
 | |
|             @foreign_keys ||= foreign_key_statements.map do |stmt|
 | |
|               stmt.relation.schemaname = schema_name if stmt.relation.schemaname == ''
 | |
| 
 | |
|               SchemaObjects::ForeignKey.new(Adapters::ForeignKeyStructureSqlAdapter.new(stmt))
 | |
|             end
 | |
|           end
 | |
| 
 | |
|           def tables
 | |
|             @tables ||= table_statements.map do |stmt|
 | |
|               table_name = stmt.relation.relname
 | |
|               partition_stmt = stmt.partspec
 | |
| 
 | |
|               columns = stmt.table_elts.select { |n| n.node == :column_def }.map do |column|
 | |
|                 adapter = Adapters::ColumnStructureSqlAdapter.new(table_name, column.column_def, partition_stmt)
 | |
|                 SchemaObjects::Column.new(adapter)
 | |
|               end
 | |
| 
 | |
|               SchemaObjects::Table.new(table_name, columns)
 | |
|             end
 | |
|           end
 | |
| 
 | |
|           private
 | |
| 
 | |
|           attr_reader :structure_file_path, :schema_name, :table_map, :index_map, :trigger_map, :foreign_key_map
 | |
| 
 | |
|           def to_map(array)
 | |
|             array.each_with_object({}) do |entry, hash| # rubocop:disable Rails/IndexBy -- This gem does not depend on ActiveSupport.
 | |
|               hash[entry.name] = entry
 | |
|             end
 | |
|           end
 | |
| 
 | |
|           def index_statements
 | |
|             statements.filter_map { |s| s.stmt.index_stmt }
 | |
|           end
 | |
| 
 | |
|           def trigger_statements
 | |
|             statements.filter_map { |s| s.stmt.create_trig_stmt }
 | |
|           end
 | |
| 
 | |
|           def table_statements
 | |
|             statements.filter_map { |s| s.stmt.create_stmt }
 | |
|           end
 | |
| 
 | |
|           def foreign_key_statements
 | |
|             constraint_statements(:CONSTR_FOREIGN)
 | |
|           end
 | |
| 
 | |
|           # Filter constraint statement nodes
 | |
|           #
 | |
|           # @param constraint_type [Symbol] node type. One of CONSTR_PRIMARY, CONSTR_CHECK, CONSTR_EXCLUSION,
 | |
|           #        CONSTR_UNIQUE or CONSTR_FOREIGN.
 | |
|           def constraint_statements(constraint_type)
 | |
|             alter_table_statements(:AT_AddConstraint).filter do |stmt|
 | |
|               stmt.cmds.first.alter_table_cmd.def.constraint.contype == constraint_type
 | |
|             end
 | |
|           end
 | |
| 
 | |
|           # Filter alter table statement nodes
 | |
|           #
 | |
|           # @param subtype [Symbol] node subtype +AT_AttachPartition+, +AT_ColumnDefault+ or +AT_AddConstraint+
 | |
|           def alter_table_statements(subtype)
 | |
|             statements.filter_map do |statement|
 | |
|               node = statement.stmt.alter_table_stmt
 | |
| 
 | |
|               next unless node
 | |
| 
 | |
|               node if node.cmds.first.alter_table_cmd.subtype == subtype
 | |
|             end
 | |
|           end
 | |
| 
 | |
|           def statements
 | |
|             @statements ||= parsed_structure_file.tree.stmts
 | |
|           end
 | |
| 
 | |
|           def parsed_structure_file
 | |
|             @parsed_structure_file ||= PgQuery.parse(File.read(structure_file_path))
 | |
|           end
 | |
| 
 | |
|           def map_with_default_schema(statements, validation_class)
 | |
|             statements.map do |statement|
 | |
|               statement.relation.schemaname = schema_name if statement.relation.schemaname == ''
 | |
| 
 | |
|               validation_class.new(statement)
 | |
|             end
 | |
|           end
 | |
|         end
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| end
 |