<commit_before># TODO: move to dm-more/dm-migrations

module DataMapper
  module Migrations
    module SingletonMethods
      # destructively migrates the repository upwards to match model definitions
      #
      # @param [Symbol] name repository to act on, :default is the default
      #
      # @api public
      def migrate!(repository_name = nil)
        repository(repository_name).migrate!
      end

      # drops and recreates the repository upwards to match model definitions
      #
      # @param [Symbol] name repository to act on, :default is the default
      #
      # @api public
      def auto_migrate!(repository_name = nil)
        auto_migrate_down!(repository_name)
        auto_migrate_up!(repository_name)
      end

      # TODO: document
      # @api public
      def auto_upgrade!(repository_name = nil)
        repository_execute(:auto_upgrade!, repository_name)
      end

      private

      # TODO: document
      # @api private
      def auto_migrate_down!(repository_name)
        repository_execute(:auto_migrate_down!, repository_name)
      end

      # TODO: document
      # @api private
      def auto_migrate_up!(repository_name)
        repository_execute(:auto_migrate_up!, repository_name)
      end

      # TODO: document
      # @api private
      def repository_execute(method, repository_name)
        DataMapper::Model.descendants.each do |model|
          model.send(method, repository_name || model.default_repository_name)
        end
      end
    end

    module DataObjectsAdapter
      # TODO: document
      # @api private
      def self.included(base)
        base.extend ClassMethods

        DataMapper.extend(Migrations::SingletonMethods)

        [ :Repository, :Model ].each do |name|
          DataMapper.const_get(name).send(:include, Migrations.const_get(name))
        end
      end

      # Returns whether the storage_name exists.
      #
      # @param [String] storage_name
      #   a String defining the name of a storage, for example a table name.
      #
      # @return [Boolean]
      #   true if the storage exists
      #
      # @api semipublic
      def storage_exists?(storage_name)
        statement = <<-SQL.compress_lines
          SELECT COUNT(*)
          FROM "information_schema"."tables"
          WHERE "table_type" = 'BASE TABLE'
          AND "table_schema" = ?
          AND "table_name" = ?
        SQL

        select(statement, schema_name, storage_name).first > 0
      end

      # Returns whether the field exists.
      #
      # @param [String] storage_name
      #   a String defining the name of a storage, for example a table name.
      # @param [String] field
      #   a String defining the name of a field, for example a column name.
      #
      # @return [Boolean]
      #   true if the field exists.
      #
      # @api semipublic
      def field_exists?(storage_name, column_name)
        statement = <<-SQL.compress_lines
          SELECT COUNT(*)
          FROM "information_schema"."columns"
          WHERE "table_schema" = ?
          AND "table_name" = ?
          AND "column_name" = ?
        SQL

        select(statement, schema_name, storage_name, column_name).first > 0
      end

      # TODO: document
      # @api semipublic
      def upgrade_model_storage(model)
        properties = model.properties_with_subclasses(name)

        if success = create_model_storage(model)
          return properties
        end

        table_name = model.storage_name(name)

        with_connection do |connection|
          properties.map do |property|
            schema_hash = property_schema_hash(property)
            next if field_exists?(table_name, schema_hash[:name])

            statement = alter_table_add_column_statement(connection, table_name, schema_hash)
            command   = connection.create_command(statement)
            command.execute_non_query

            property
          end.compact
        end
      end

      # TODO: document
      # @api semipublic
      def create_model_storage(model)
        properties = model.properties_with_subclasses(name)

        return false if storage_exists?(model.storage_name(name))
        return false if properties.empty?

        with_connection do |connection|
          statement = create_table_statement(connection, model, properties)
          command   = connection.create_command(statement)
          command.execute_non_query

          (create_index_statements(model) + create_unique_index_statements(model)).each do |statement|
            command   = connection.create_command(statement)
            command.execute_non_query
          end
        end

        true
      end

      # TODO: document
      # @api semipublic
      def destroy_model_storage(model)
        return true unless supports_drop_table_if_exists? || storage_exists?(model.storage_name(name))
        execute(drop_table_statement(model))
        true
      end

      module SQL #:nodoc:
#        private  ## This cannot be private for current migrations

        # Adapters that support AUTO INCREMENT fields for CREATE TABLE
        # statements should overwrite this to return true
        #
        # @api private
        def supports_serial?
          false
        end

        # TODO: document
        # @api private
        def supports_drop_table_if_exists?
          false
        end

        # TODO: document
        # @api private
        def schema_name
          raise NotImplementedError, "#{self.class}#schema_name not implemented"
        end

        # TODO: document
        # @api private
        def alter_table_add_column_statement(connection, table_name, schema_hash)
          "ALTER TABLE #{quote_name(table_name)} ADD COLUMN #{property_schema_statement(connection, schema_hash)}"
        end

        # TODO: document
        # @api private
        def create_table_statement(connection, model, properties)
          statement = <<-SQL.compress_lines
            CREATE TABLE #{quote_name(model.storage_name(name))}
            (#{properties.map { |property| property_schema_statement(connection, property_schema_hash(property)) }.join(', ')},
            PRIMARY KEY(#{ properties.key.map { |property| quote_name(property.field) }.join(', ')}))
          SQL

          statement
        end

        # TODO: document
        # @api private
        def drop_table_statement(model)
          if supports_drop_table_if_exists?
            "DROP TABLE IF EXISTS #{quote_name(model.storage_name(name))}"
          else
            "DROP TABLE #{quote_name(model.storage_name(name))}"
          end
        end

        # TODO: document
        # @api private
        def create_index_statements(model)
          table_name = model.storage_name(name)
          model.properties(name).indexes.map do |index_name, fields|
            <<-SQL.compress_lines
              CREATE INDEX #{quote_name("index_#{table_name}_#{index_name}")} ON
              #{quote_name(table_name)} (#{fields.map { |field| quote_name(field) }.join(', ')})
            SQL
          end
        end

        # TODO: document
        # @api private
        def create_unique_index_statements(model)
          table_name = model.storage_name(name)
          model.properties(name).unique_indexes.map do |index_name, fields|
            <<-SQL.compress_lines
              CREATE UNIQUE INDEX #{quote_name("unique_#{table_name}_#{index_name}")} ON
              #{quote_name(table_name)} (#{fields.map { |field| quote_name(field) }.join(', ')})
            SQL
          end
        end

        # TODO: document
        # @api private
        def property_schema_hash(property)
          schema = (self.class.type_map[property.type] || self.class.type_map[property.primitive]).merge(:name => property.field)

          if property.primitive == String && schema[:primitive] != 'TEXT' && schema[:primitive] != 'CLOB'
            schema[:length] = property.length
          elsif property.primitive == BigDecimal || property.primitive == Float
            schema[:precision] = property.precision
            schema[:scale]     = property.scale
          end

          schema[:nullable] = property.nullable?
          schema[:serial]   = property.serial?

          if property.default.nil? || property.default.respond_to?(:call)
            # remove the default if the property is not nullable
            schema.delete(:default) unless property.nullable?
          else
            if property.type.respond_to?(:dump)
              schema[:default] = property.type.dump(property.default, property)
            else
              schema[:default] = property.default
            end
          end

          schema
        end

        # TODO: document
        # @api private
        def property_schema_statement(connection, schema)
          statement = quote_name(schema[:name])
          statement << " #{schema[:primitive]}"

          if schema[:precision] && schema[:scale]
            statement << "(#{[ :precision, :scale ].map { |key| connection.quote_value(schema[key]) }.join(', ')})"
          elsif schema[:length]
            statement << "(#{connection.quote_value(schema[:length])})"
          end

          statement << " DEFAULT #{connection.quote_value(schema[:default])}" if schema.key?(:default)
          statement << ' NOT NULL' unless schema[:nullable]
          statement
        end
      end # module SQL

      include SQL

      module ClassMethods
        # Default types for all data object based adapters.
        #
        # @return [Hash] default types for data objects adapters.
        #
        # @api private
        def type_map
          length    = Property::DEFAULT_LENGTH
          precision = Property::DEFAULT_PRECISION
          scale     = Property::DEFAULT_SCALE_BIGDECIMAL

          @type_map ||= {
            Integer     => { :primitive => 'INTEGER'                                           },
            String      => { :primitive => 'VARCHAR', :length => length                        },
            Class       => { :primitive => 'VARCHAR', :length => length                        },
            BigDecimal  => { :primitive => 'DECIMAL', :precision => precision, :scale => scale },
            Float       => { :primitive => 'FLOAT',   :precision => precision                  },
            DateTime    => { :primitive => 'TIMESTAMP'                                         },
            Date        => { :primitive => 'DATE'                                              },
            Time        => { :primitive => 'TIMESTAMP'                                         },
            TrueClass   => { :primitive => 'BOOLEAN'                                           },
            Types::Text => { :primitive => 'TEXT'                                              },
          }.freeze
        end
      end # module ClassMethods
    end # module DataObjectsAdapter

    module MysqlAdapter
      DEFAULT_ENGINE        = 'InnoDB'.freeze
      DEFAULT_CHARACTER_SET = 'utf8'.freeze
      DEFAULT_COLLATION     = 'utf8_unicode_ci'.freeze

      # TODO: document
      # @api private
      def self.included(base)
        base.extend ClassMethods
      end

      # TODO: document
      # @api semipublic
      def storage_exists?(storage_name)
        select('SHOW TABLES LIKE ?', storage_name).first == storage_name
      end

      # TODO: document
      # @api semipublic
      def field_exists?(storage_name, field)
        result = select("SHOW COLUMNS FROM #{quote_name(storage_name)} LIKE ?", field).first
        result ? result.field == field : false
      end

      module SQL #:nodoc:
#        private  ## This cannot be private for current migrations

        # TODO: document
        # @api private
        def supports_serial?
          true
        end

        # TODO: document
        # @api private
        def supports_drop_table_if_exists?
          true
        end

        # TODO: document
        # @api private
        def schema_name
          # TODO: is there a cleaner way to find out the current DB we are connected to?
          normalized_uri.path.split('/').last
        end

        # TODO: update dkubb/dm-more/dm-migrations to use schema_name and remove this
        alias db_name schema_name

        # TODO: document
        # @api private
        def create_table_statement(connection, model, properties)
          "#{super} ENGINE = #{DEFAULT_ENGINE} CHARACTER SET #{character_set} COLLATE #{collation}"
        end

        # TODO: document
        # @api private
        def property_schema_hash(property)
          schema = super

          if property.primitive == Types::Text
            schema[:primitive] = text_column_statement(property.length)
            schema.delete(:default)
          end

          if property.primitive == Integer && property.min && property.max
            schema[:primitive] = integer_column_statement(property.min..property.max)
          end

          schema
        end

        # TODO: document
        # @api private
        def property_schema_statement(connection, schema)<commit_msg>Fixed problem with MySQL TEXT columns lengths not being set for Text types<commit_after># TODO: move to dm-more/dm-migrations

module DataMapper
  module Migrations
    module SingletonMethods
      # destructively migrates the repository upwards to match model definitions
      #
      # @param [Symbol] name repository to act on, :default is the default
      #
      # @api public
      def migrate!(repository_name = nil)
        repository(repository_name).migrate!
      end

      # drops and recreates the repository upwards to match model definitions
      #
      # @param [Symbol] name repository to act on, :default is the default
      #
      # @api public
      def auto_migrate!(repository_name = nil)
        auto_migrate_down!(repository_name)
        auto_migrate_up!(repository_name)
      end

      # TODO: document
      # @api public
      def auto_upgrade!(repository_name = nil)
        repository_execute(:auto_upgrade!, repository_name)
      end

      private

      # TODO: document
      # @api private
      def auto_migrate_down!(repository_name)
        repository_execute(:auto_migrate_down!, repository_name)
      end

      # TODO: document
      # @api private
      def auto_migrate_up!(repository_name)
        repository_execute(:auto_migrate_up!, repository_name)
      end

      # TODO: document
      # @api private
      def repository_execute(method, repository_name)
        DataMapper::Model.descendants.each do |model|
          model.send(method, repository_name || model.default_repository_name)
        end
      end
    end

    module DataObjectsAdapter
      # TODO: document
      # @api private
      def self.included(base)
        base.extend ClassMethods

        DataMapper.extend(Migrations::SingletonMethods)

        [ :Repository, :Model ].each do |name|
          DataMapper.const_get(name).send(:include, Migrations.const_get(name))
        end
      end

      # Returns whether the storage_name exists.
      #
      # @param [String] storage_name
      #   a String defining the name of a storage, for example a table name.
      #
      # @return [Boolean]
      #   true if the storage exists
      #
      # @api semipublic
      def storage_exists?(storage_name)
        statement = <<-SQL.compress_lines
          SELECT COUNT(*)
          FROM "information_schema"."tables"
          WHERE "table_type" = 'BASE TABLE'
          AND "table_schema" = ?
          AND "table_name" = ?
        SQL

        select(statement, schema_name, storage_name).first > 0
      end

      # Returns whether the field exists.
      #
      # @param [String] storage_name
      #   a String defining the name of a storage, for example a table name.
      # @param [String] field
      #   a String defining the name of a field, for example a column name.
      #
      # @return [Boolean]
      #   true if the field exists.
      #
      # @api semipublic
      def field_exists?(storage_name, column_name)
        statement = <<-SQL.compress_lines
          SELECT COUNT(*)
          FROM "information_schema"."columns"
          WHERE "table_schema" = ?
          AND "table_name" = ?
          AND "column_name" = ?
        SQL

        select(statement, schema_name, storage_name, column_name).first > 0
      end

      # TODO: document
      # @api semipublic
      def upgrade_model_storage(model)
        properties = model.properties_with_subclasses(name)

        if success = create_model_storage(model)
          return properties
        end

        table_name = model.storage_name(name)

        with_connection do |connection|
          properties.map do |property|
            schema_hash = property_schema_hash(property)
            next if field_exists?(table_name, schema_hash[:name])

            statement = alter_table_add_column_statement(connection, table_name, schema_hash)
            command   = connection.create_command(statement)
            command.execute_non_query

            property
          end.compact
        end
      end

      # TODO: document
      # @api semipublic
      def create_model_storage(model)
        properties = model.properties_with_subclasses(name)

        return false if storage_exists?(model.storage_name(name))
        return false if properties.empty?

        with_connection do |connection|
          statement = create_table_statement(connection, model, properties)
          command   = connection.create_command(statement)
          command.execute_non_query

          (create_index_statements(model) + create_unique_index_statements(model)).each do |statement|
            command   = connection.create_command(statement)
            command.execute_non_query
          end
        end

        true
      end

      # TODO: document
      # @api semipublic
      def destroy_model_storage(model)
        return true unless supports_drop_table_if_exists? || storage_exists?(model.storage_name(name))
        execute(drop_table_statement(model))
        true
      end

      module SQL #:nodoc:
#        private  ## This cannot be private for current migrations

        # Adapters that support AUTO INCREMENT fields for CREATE TABLE
        # statements should overwrite this to return true
        #
        # @api private
        def supports_serial?
          false
        end

        # TODO: document
        # @api private
        def supports_drop_table_if_exists?
          false
        end

        # TODO: document
        # @api private
        def schema_name
          raise NotImplementedError, "#{self.class}#schema_name not implemented"
        end

        # TODO: document
        # @api private
        def alter_table_add_column_statement(connection, table_name, schema_hash)
          "ALTER TABLE #{quote_name(table_name)} ADD COLUMN #{property_schema_statement(connection, schema_hash)}"
        end

        # TODO: document
        # @api private
        def create_table_statement(connection, model, properties)
          statement = <<-SQL.compress_lines
            CREATE TABLE #{quote_name(model.storage_name(name))}
            (#{properties.map { |property| property_schema_statement(connection, property_schema_hash(property)) }.join(', ')},
            PRIMARY KEY(#{ properties.key.map { |property| quote_name(property.field) }.join(', ')}))
          SQL

          statement
        end

        # TODO: document
        # @api private
        def drop_table_statement(model)
          if supports_drop_table_if_exists?
            "DROP TABLE IF EXISTS #{quote_name(model.storage_name(name))}"
          else
            "DROP TABLE #{quote_name(model.storage_name(name))}"
          end
        end

        # TODO: document
        # @api private
        def create_index_statements(model)
          table_name = model.storage_name(name)
          model.properties(name).indexes.map do |index_name, fields|
            <<-SQL.compress_lines
              CREATE INDEX #{quote_name("index_#{table_name}_#{index_name}")} ON
              #{quote_name(table_name)} (#{fields.map { |field| quote_name(field) }.join(', ')})
            SQL
          end
        end

        # TODO: document
        # @api private
        def create_unique_index_statements(model)
          table_name = model.storage_name(name)
          model.properties(name).unique_indexes.map do |index_name, fields|
            <<-SQL.compress_lines
              CREATE UNIQUE INDEX #{quote_name("unique_#{table_name}_#{index_name}")} ON
              #{quote_name(table_name)} (#{fields.map { |field| quote_name(field) }.join(', ')})
            SQL
          end
        end

        # TODO: document
        # @api private
        def property_schema_hash(property)
          schema = (self.class.type_map[property.type] || self.class.type_map[property.primitive]).merge(:name => property.field)

          if property.primitive == String && schema[:primitive] != 'TEXT' && schema[:primitive] != 'CLOB'
            schema[:length] = property.length
          elsif property.primitive == BigDecimal || property.primitive == Float
            schema[:precision] = property.precision
            schema[:scale]     = property.scale
          end

          schema[:nullable] = property.nullable?
          schema[:serial]   = property.serial?

          if property.default.nil? || property.default.respond_to?(:call)
            # remove the default if the property is not nullable
            schema.delete(:default) unless property.nullable?
          else
            if property.type.respond_to?(:dump)
              schema[:default] = property.type.dump(property.default, property)
            else
              schema[:default] = property.default
            end
          end

          schema
        end

        # TODO: document
        # @api private
        def property_schema_statement(connection, schema)
          statement = quote_name(schema[:name])
          statement << " #{schema[:primitive]}"

          if schema[:precision] && schema[:scale]
            statement << "(#{[ :precision, :scale ].map { |key| connection.quote_value(schema[key]) }.join(', ')})"
          elsif schema[:length]
            statement << "(#{connection.quote_value(schema[:length])})"
          end

          statement << " DEFAULT #{connection.quote_value(schema[:default])}" if schema.key?(:default)
          statement << ' NOT NULL' unless schema[:nullable]
          statement
        end
      end # module SQL

      include SQL

      module ClassMethods
        # Default types for all data object based adapters.
        #
        # @return [Hash] default types for data objects adapters.
        #
        # @api private
        def type_map
          length    = Property::DEFAULT_LENGTH
          precision = Property::DEFAULT_PRECISION
          scale     = Property::DEFAULT_SCALE_BIGDECIMAL

          @type_map ||= {
            Integer     => { :primitive => 'INTEGER'                                           },
            String      => { :primitive => 'VARCHAR', :length => length                        },
            Class       => { :primitive => 'VARCHAR', :length => length                        },
            BigDecimal  => { :primitive => 'DECIMAL', :precision => precision, :scale => scale },
            Float       => { :primitive => 'FLOAT',   :precision => precision                  },
            DateTime    => { :primitive => 'TIMESTAMP'                                         },
            Date        => { :primitive => 'DATE'                                              },
            Time        => { :primitive => 'TIMESTAMP'                                         },
            TrueClass   => { :primitive => 'BOOLEAN'                                           },
            Types::Text => { :primitive => 'TEXT'                                              },
          }.freeze
        end
      end # module ClassMethods
    end # module DataObjectsAdapter

    module MysqlAdapter
      DEFAULT_ENGINE        = 'InnoDB'.freeze
      DEFAULT_CHARACTER_SET = 'utf8'.freeze
      DEFAULT_COLLATION     = 'utf8_unicode_ci'.freeze

      # TODO: document
      # @api private
      def self.included(base)
        base.extend ClassMethods
      end

      # TODO: document
      # @api semipublic
      def storage_exists?(storage_name)
        select('SHOW TABLES LIKE ?', storage_name).first == storage_name
      end

      # TODO: document
      # @api semipublic
      def field_exists?(storage_name, field)
        result = select("SHOW COLUMNS FROM #{quote_name(storage_name)} LIKE ?", field).first
        result ? result.field == field : false
      end

      module SQL #:nodoc:
#        private  ## This cannot be private for current migrations

        # TODO: document
        # @api private
        def supports_serial?
          true
        end

        # TODO: document
        # @api private
        def supports_drop_table_if_exists?
          true
        end

        # TODO: document
        # @api private
        def schema_name
          # TODO: is there a cleaner way to find out the current DB we are connected to?
          normalized_uri.path.split('/').last
        end

        # TODO: update dkubb/dm-more/dm-migrations to use schema_name and remove this
        alias db_name schema_name

        # TODO: document
        # @api private
        def create_table_statement(connection, model, properties)
          "#{super} ENGINE = #{DEFAULT_ENGINE} CHARACTER SET #{character_set} COLLATE #{collation}"
        end

        # TODO: document
        # @api private
        def property_schema_hash(property)
          schema = super

          if schema[:primitive] == 'TEXT'
            schema[:primitive] = text_column_statement(property.length)
            schema.delete(:default)
          end

          if property.primitive == Integer && property.min && property.max
            schema[:primitive] = integer_column_statement(property.min..property.max)
          end

          schema
        end

        # TODO: document
        # @api private
        def property_schema_statement(connection, schema)
{'fraction_non_alphanumeric': 0.08899725693386162, 'fraction_numerical': 0.00030478512648582747, 'mean_word_length': 1.9579670948839305, 'pattern_counts': {'":': 0, '<': 43, '<?xml version=': 0, '>': 59, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 20, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2727822', 'n_tokens_mistral': 6917, 'n_tokens_neox': 6750, 'n_words': 1713}