Example #1
0
def _generate_index_unique_name_hash(connection, table, col_names):
    """Return the hash for the unique part of an index name.

    Args:
        connection (django.db.backends.base.base.BaseDatabaseWrapper):
            The database connection.

        table (unicode):
            The name of the table.

        col_names (list of unicode):
            The list of column names for the index.

    Returns:
        unicode:
        A hash for the unique part of an index.
    """
    assert isinstance(col_names, list)

    if django.VERSION[:2] >= (1, 9):
        # Django >= 1.9
        #
        # Django 1.9 introduced a new format for the unique index hashes,
        # switching back to using digest() instead of hash().
        return digest(connection, table, *col_names)
    else:
        # Django >= 1.7, < 1.9
        return '%x' % abs(hash((table, ','.join(col_names))))
Example #2
0
def _generate_index_unique_name_hash(connection, table, col_names):
    """Return the hash for the unique part of an index name.

    This is used on Django 1.7+ to generate a unique hash as part of an
    index name.

    Args:
        connection (object):
            The database connection.

        table (str):
            The name of the table.

        col_names (list of str):
            The list of column names for the index.

    Returns:
        str:
        A hash for the unique part of an index.
    """
    assert isinstance(col_names, list)

    if django.VERSION[:2] >= (1, 9):
        # Django >= 1.9
        #
        # Django 1.9 introduced a new format for the unique index hashes,
        # switching back to using digest() instead of hash().
        return '_%s' % digest(connection, table, *col_names)
    else:
        # Django >= 1.7, < 1.9
        return '_%x' % abs(hash((table, ','.join(col_names))))
Example #3
0
def _generate_index_unique_name_hash(connection, table, col_names):
    """Return the hash for the unique part of an index name.

    Args:
        connection (django.db.backends.base.base.BaseDatabaseWrapper):
            The database connection.

        table (unicode):
            The name of the table.

        col_names (list of unicode):
            The list of column names for the index.

    Returns:
        unicode:
        A hash for the unique part of an index.
    """
    assert isinstance(col_names, list)

    if django.VERSION[:2] >= (1, 9):
        # Django >= 1.9
        #
        # Django 1.9 introduced a new format for the unique index hashes,
        # switching back to using digest() instead of hash().
        return digest(connection, table, *col_names)
    else:
        # Django >= 1.7, < 1.9
        return '%x' % abs(hash((table, ','.join(col_names))))
Example #4
0
def generate_unique_constraint_name(table, col_names):
    """Return the expected name for a unique constraint.

    This will generate a constraint name for the current version of Django,
    for comparison purposes.

    Args:
        table (str):
            The table name.

        col_names (list of str):
            The list of column names for the constraint.

    Returns:
        The expected constraint name for this version of Django.
    """
    if django.VERSION[:2] >= (1, 7):
        max_length = connection.ops.max_name_length() or 200
        index_unique_name = _generate_index_unique_name_hash(
            connection, table, col_names)
        name = '_%s%s_uniq' % (col_names[0], index_unique_name)
        full_name = '%s%s' % (table, name)

        if len(full_name) > max_length:
            full_name = '%s%s' % (table[:(max_length - len(name))], name)

        return full_name
    else:
        name = digest(connection, col_names)

        return truncate_name('%s_%s' % (table, name),
                             connection.ops.max_name_length())
Example #5
0
def generate_constraint_name(db_type, r_col, col, r_table, table):
    """Return the expected name for a constraint.

    This will generate a constraint name for the current version of Django,
    for comparison purposes.

    Args:
        db_type (str):
            The type of database.

        r_col (str):
            The column name for the source of the relation.

        col (str):
            The column name for the "to" end of the relation.

        r_table (str):
            The table name for the source of the relation.

        table (str):
            The table name for the "to" end of the relation.

    Returns:
        str:
        The expected name for a constraint.
    """
    if django.VERSION[:2] >= (1, 7):
        # This is an approximation of what Django 1.7+ uses for constraint
        # naming. It's actually the same as index naming, but for test
        # purposes, we want to keep this distinct from the index naming above.
        # It also doesn't cover all the cases that
        # BaseDatabaseSchemaEditor._create_index_name covers, but they're not
        # necessary for our tests (and we'll know if it all blows up somehow).
        max_length = connection.ops.max_name_length() or 200
        index_unique_name = _generate_index_unique_name_hash(
            connection, r_table, [r_col])

        name = '_%s%s_fk_%s_%s' % (r_col, index_unique_name, table, col)
        full_name = '%s%s' % (r_table, name)

        if len(full_name) > max_length:
            full_name = '%s%s' % (r_table[:(max_length - len(name))], name)

        return full_name
    else:
        return '%s_refs_%s_%s' % (r_col, col,
                                  digest(connection, r_table, table))
Example #6
0
def generate_unique_constraint_name(connection, table, col_names):
    """Return the expected name for a unique constraint.

    This will generate a constraint name for the current version of Django,
    for comparison purposes.

    Args:
        connection (django.db.backends.base.base.BaseDatabaseWrapper):
            The database connection.

        table (unicode):
            The table name.

        col_names (list of unicode):
            The list of column names for the constraint.

    Returns:
        unicode:
        The expected constraint name for this version of Django.
    """
    django_version = django.VERSION[:2]

    if django_version >= (1, 11):
        # Django 1.11 changed how index names are generated and then
        # shortened, choosing to shorten more preemptively. This does impact
        # the tests, so we need to be sure to get the logic right.
        max_length = connection.ops.max_name_length() or 200
        index_unique_name = _generate_index_unique_name_hash(
            connection, table, col_names)

        suffix = '%s_uniq' % index_unique_name
        col_names_part = '_'.join(col_names)
        full_name = '%s_%s_%s' % (table, col_names_part, suffix)

        if len(full_name) > max_length:
            if len(suffix) > (max_length // 3):
                suffix = suffix[:max_length // 3]

            part_lengths = (max_length - len(suffix)) // 2 - 1
            full_name = '%s_%s_%s' % (table[:part_lengths],
                                      col_names_part[:part_lengths],
                                      suffix)

        return full_name
    elif django_version >= (1, 7):
        # Django versions >= 1.7 all use roughly the same format for unique
        # constraint index names, but starting in Django 1.11, the format
        # changed slightly. In 1.7 through 1.10, the name contained only the
        # first column (if specifying more than one), but in 1.11, that
        # changed to contain all column names (for unique_together).
        max_length = connection.ops.max_name_length() or 200
        index_unique_name = _generate_index_unique_name_hash(
            connection, table, col_names)

        name = '_%s_%s_uniq' % (col_names[0], index_unique_name)
        full_name = '%s%s' % (table, name)

        if len(full_name) > max_length:
            full_name = '%s%s' % (table[:(max_length - len(name))], name)

        return full_name
    else:
        # Convert each of the field names to Python's native string format,
        # which is what the default name would normally be in.
        name = digest(connection, [
            str(col_name)
            for col_name in col_names
        ])

        return truncate_name('%s_%s' % (table, name),
                             connection.ops.max_name_length())
Example #7
0
def generate_constraint_name(connection, r_col, col, r_table, table):
    """Return the expected name for a constraint.

    This will generate a constraint name for the current version of Django,
    for comparison purposes.

    Args:
        connection (django.db.backends.base.base.BaseDatabaseWrapper):
            The database connection.

        r_col (unicode):
            The column name for the source of the relation.

        col (unicode):
            The column name for the "to" end of the relation.

        r_table (unicode):
            The table name for the source of the relation.

        table (unicode):
            The table name for the "to" end of the relation.

    Returns:
        unicode:
        The expected name for a constraint.
    """
    django_version = django.VERSION[:2]

    if django_version >= (1, 11):
        # Django 1.11 changed how index names are generated and then
        # shortened, choosing to shorten more preemptively. This does impact
        # the tests, so we need to be sure to get the logic right.
        max_length = connection.ops.max_name_length() or 200
        index_unique_name = _generate_index_unique_name_hash(
            connection, r_table, [r_col])
        suffix = '%s_fk_%s_%s' % (index_unique_name, table, col)
        full_name = '%s_%s_%s' % (r_table, r_col, suffix)

        if len(full_name) > max_length:
            if len(suffix) > (max_length // 3):
                suffix = suffix[:max_length // 3]

            part_lengths = (max_length - len(suffix)) // 2 - 1
            full_name = '%s_%s_%s' % (r_table[:part_lengths],
                                      r_col[:part_lengths],
                                      suffix)

        return full_name
    elif django_version >= (1, 7):
        # This is an approximation of what Django 1.7+ uses for constraint
        # naming. It's actually the same as index naming, but for test
        # purposes, we want to keep this distinct from the index naming above.
        # It also doesn't cover all the cases that
        # BaseDatabaseSchemaEditor._create_index_name covers, but they're not
        # necessary for our tests (and we'll know if it all blows up somehow).
        max_length = connection.ops.max_name_length() or 200
        index_unique_name = _generate_index_unique_name_hash(
            connection, r_table, [r_col])

        name = '_%s_%s_fk_%s_%s' % (r_col, index_unique_name, table, col)
        full_name = '%s%s' % (r_table, name)

        if len(full_name) > max_length:
            full_name = '%s%s' % (r_table[:(max_length - len(name))], name)

        return full_name
    else:
        return '%s_refs_%s_%s' % (r_col, col,
                                  digest(connection, r_table, table))
Example #8
0
def generate_index_name(connection, table, col_names, field_names=None,
                        index_together=False, model_meta_indexes=False):
    """Generate a suitable index name to test against.

    The returned index name is meant for use in the test data modules, and
    is used to compare our own expectations of how an index should be named
    with the naming Django provides in its own functions.

    Args:
        connection (django.db.backends.base.base.BaseDatabaseWrapper):
            The database connection.

        table (unicode):
            The name of the table the index refers to.

        col_names (unicode or list of unicode):
            The column name, or list of column names, for the index.

            This is used for Postgres (when not using ``index_together``),
            or for Django < 1.5. Otherwise, it's interchangeable with
            ``field_names``.

        field_names (str or list of str, optional):
            The field name, or list of field names, for the index.

            This is interchangeable with ``column_names`` on Django >= 1.5
            (unless using Postgres without ``index_together``), or when
            passing ``default=True``.

        index_together (bool, optional):
            Whether this index covers multiple fields indexed together
            through Django's ``Model._meta.index_together``.

            Defaults to ``False``.

        model_meta_indexes (bool, optional):
            The index comes from a
            :py:class:`django.db.models.Options.indexes` entry.

    Returns:
        unicode:
        The resulting index name for the given criteria.
    """
    if not isinstance(col_names, list):
        col_names = [col_names]

    if field_names and not isinstance(field_names, list):
        field_names = [field_names]

    if not field_names:
        field_names = col_names

    assert len(field_names) == len(col_names)

    django_version = django.VERSION[:2]

    # Note that we're checking Django versions/engines specifically, since
    # we want to test that we're getting the right index names for the
    # right versions of Django, rather than asking Django for them.
    #
    # The order here matters.
    if django_version >= (1, 11):
        # Django 1.11+ changed the index format again, this time to include
        # all relevant column names in the plain text part of the index
        # (instead of just in the hash). Like with 1.7 through 1.10, the
        # index_together entries have a "_idx" suffix. However, there's
        # otherwise no difference in format between those and single-column
        # indexes.
        #
        # It's also worth noting that with the introduction of
        # Model._meta.indexes, there's *another* new index format. It's
        # similar, but different enough, and needs to be handled specially.
        if model_meta_indexes:
            name = '%s_%s' % (
                col_names[0][:7],
                digest(connection, *([table] + col_names + ['idx']))[:6],
            )
            table = table[:11]
        else:
            index_unique_name = _generate_index_unique_name_hash(
                connection, table, col_names)
            name = '%s_%s' % ('_'.join(col_names), index_unique_name)

        if model_meta_indexes or index_together:
            name = '%s_idx' % name
    elif django_version >= (1, 7):
        if len(col_names) == 1:
            assert not index_together

            # Django 1.7 went back to passing a single column name (and
            # not a list as a single variable argument) when there's only
            # one column.
            name = digest(connection, col_names[0])
        else:
            assert index_together

            index_unique_name = _generate_index_unique_name_hash(
                connection, table, col_names)
            name = '%s_%s_idx' % (col_names[0], index_unique_name)
    elif connection.vendor == 'postgresql' and not index_together:
        # Postgres computes the index names separately from the rest of
        # the engines. It just uses '<tablename>_<colname>", same as
        # Django < 1.2. We only do this for normal indexes, though, not
        # index_together.
        name = col_names[0]
    elif django_version >= (1, 5):
        # Django >= 1.5 computed the digest of the representation of a
        # list of either field names or column names. Note that digest()
        # takes variable positional arguments, which this is not passing.
        # This is due to a design bug in these versions.
        #
        # We convert each of the field names to Python's native string
        # format, which is what the default name would normally be in.
        name = digest(connection, [
            str(field_name)
            for field_name in (field_names or col_names)
        ])
    elif django_version >= (1, 2):
        # Django >= 1.2, < 1.7 used the digest of the name of the first
        # column. There was no index_together in these releases.
        name = digest(connection, col_names[0])
    else:
        # Django < 1.2 used just the name of the first column, no digest.
        name = col_names[0]

    return truncate_name('%s_%s' % (table, name),
                         connection.ops.max_name_length())
Example #9
0
def generate_index_name(db_type, table, col_names, field_names=None,
                        index_together=False):
    """Generate a suitable index name to test against.

    The returned index name is meant for use in the test data modules, and
    is used to compare our own expectations of how an index should be named
    with the naming Django provides in its own functions.

    Args:
        db_type (str):
            The database type for the index. Currently, only "postgres"
            does anything special.

        table (str):
            The name of the table the index refers to.

        col_names (str or list of str):
            The column name, or list of column names, for the index.

            This is used for Postgres (when not using ``index_together``),
            or for Django < 1.5. Otherwise, it's interchangeable with
            ``field_names``.

        field_names (str or list of str, optional):
            The field name, or list of field names, for the index.

            This is interchangeable with ``column_names`` on Django >= 1.5
            (unless using Postgres without ``index_together``), or when
            passing ``default=True``.

        index_together (bool, optional):
            Whether this index covers multiple fields indexed together
            through Django's ``Model._meta.index_together``.

            Defaults to ``False``.

    Returns:
        str:
        The resulting index name for the given criteria.
    """
    if not isinstance(col_names, list):
        col_names = [col_names]

    if field_names and not isinstance(field_names, list):
        field_names = [field_names]

    if not field_names:
        field_names = col_names

    assert len(field_names) == len(col_names)

    django_version = django.VERSION[:2]

    # Note that we're checking Django versions/engines specifically, since
    # we want to test that we're getting the right index names for the
    # right versions of Django, rather than asking Django for them.
    #
    # The order here matters.
    if django_version >= (1, 7):
        if len(col_names) == 1:
            assert not index_together

            # Django 1.7 went back to passing a single column name (and
            # not a list as a single variable argument) when there's only
            # one column.
            name = digest(connection, col_names[0])
        else:
            assert index_together

            index_unique_name = _generate_index_unique_name_hash(
                connection, table, col_names)
            name = '%s%s_idx' % (col_names[0], index_unique_name)
    elif db_type == 'postgres' and not index_together:
        # Postgres computes the index names separately from the rest of
        # the engines. It just uses '<tablename>_<colname>", same as
        # Django < 1.2. We only do this for normal indexes, though, not
        # index_together.
        name = col_names[0]
    elif django_version >= (1, 5):
        # Django >= 1.5 computed the digest of the representation of a
        # list of either field names or column names. Note that digest()
        # takes variable positional arguments, which this is not passing.
        # This is due to a design bug in these versions.
        name = digest(connection, field_names or col_names)
    elif django_version >= (1, 2):
        # Django >= 1.2, < 1.7 used the digest of the name of the first
        # column. There was no index_together in these releases.
        name = digest(connection, col_names[0])
    else:
        # Django < 1.2 used just the name of the first column, no digest.
        name = col_names[0]

    return truncate_name('%s_%s' % (table, name),
                         connection.ops.max_name_length())
Example #10
0
def generate_unique_constraint_name(connection, table, col_names):
    """Return the expected name for a unique constraint.

    This will generate a constraint name for the current version of Django,
    for comparison purposes.

    Args:
        connection (django.db.backends.base.base.BaseDatabaseWrapper):
            The database connection.

        table (unicode):
            The table name.

        col_names (list of unicode):
            The list of column names for the constraint.

    Returns:
        unicode:
        The expected constraint name for this version of Django.
    """
    django_version = django.VERSION[:2]

    if django_version >= (1, 11):
        # Django 1.11 changed how index names are generated and then
        # shortened, choosing to shorten more preemptively. This does impact
        # the tests, so we need to be sure to get the logic right.
        max_length = connection.ops.max_name_length() or 200
        index_unique_name = _generate_index_unique_name_hash(
            connection, table, col_names)

        suffix = '%s_uniq' % index_unique_name
        col_names_part = '_'.join(col_names)
        full_name = '%s_%s_%s' % (table, col_names_part, suffix)

        if len(full_name) > max_length:
            if len(suffix) > (max_length // 3):
                suffix = suffix[:max_length // 3]

            part_lengths = (max_length - len(suffix)) // 2 - 1
            full_name = '%s_%s_%s' % (table[:part_lengths],
                                      col_names_part[:part_lengths],
                                      suffix)

        return full_name
    elif django_version >= (1, 7):
        # Django versions >= 1.7 all use roughly the same format for unique
        # constraint index names, but starting in Django 1.11, the format
        # changed slightly. In 1.7 through 1.10, the name contained only the
        # first column (if specifying more than one), but in 1.11, that
        # changed to contain all column names (for unique_together).
        max_length = connection.ops.max_name_length() or 200
        index_unique_name = _generate_index_unique_name_hash(
            connection, table, col_names)

        name = '_%s_%s_uniq' % (col_names[0], index_unique_name)
        full_name = '%s%s' % (table, name)

        if len(full_name) > max_length:
            full_name = '%s%s' % (table[:(max_length - len(name))], name)

        return full_name
    else:
        # Convert each of the field names to Python's native string format,
        # which is what the default name would normally be in.
        name = digest(connection, [
            str(col_name)
            for col_name in col_names
        ])

        return truncate_name('%s_%s' % (table, name),
                             connection.ops.max_name_length())
Example #11
0
def generate_constraint_name(connection, r_col, col, r_table, table):
    """Return the expected name for a constraint.

    This will generate a constraint name for the current version of Django,
    for comparison purposes.

    Args:
        connection (django.db.backends.base.base.BaseDatabaseWrapper):
            The database connection.

        r_col (unicode):
            The column name for the source of the relation.

        col (unicode):
            The column name for the "to" end of the relation.

        r_table (unicode):
            The table name for the source of the relation.

        table (unicode):
            The table name for the "to" end of the relation.

    Returns:
        unicode:
        The expected name for a constraint.
    """
    django_version = django.VERSION[:2]

    if django_version >= (1, 11):
        # Django 1.11 changed how index names are generated and then
        # shortened, choosing to shorten more preemptively. This does impact
        # the tests, so we need to be sure to get the logic right.
        max_length = connection.ops.max_name_length() or 200
        index_unique_name = _generate_index_unique_name_hash(
            connection, r_table, [r_col])
        suffix = '%s_fk_%s_%s' % (index_unique_name, table, col)
        full_name = '%s_%s_%s' % (r_table, r_col, suffix)

        if len(full_name) > max_length:
            if len(suffix) > (max_length // 3):
                suffix = suffix[:max_length // 3]

            part_lengths = (max_length - len(suffix)) // 2 - 1
            full_name = '%s_%s_%s' % (r_table[:part_lengths],
                                      r_col[:part_lengths],
                                      suffix)

        return full_name
    elif django_version >= (1, 7):
        # This is an approximation of what Django 1.7+ uses for constraint
        # naming. It's actually the same as index naming, but for test
        # purposes, we want to keep this distinct from the index naming above.
        # It also doesn't cover all the cases that
        # BaseDatabaseSchemaEditor._create_index_name covers, but they're not
        # necessary for our tests (and we'll know if it all blows up somehow).
        max_length = connection.ops.max_name_length() or 200
        index_unique_name = _generate_index_unique_name_hash(
            connection, r_table, [r_col])

        name = '_%s_%s_fk_%s_%s' % (r_col, index_unique_name, table, col)
        full_name = '%s%s' % (r_table, name)

        if len(full_name) > max_length:
            full_name = '%s%s' % (r_table[:(max_length - len(name))], name)

        return full_name
    else:
        return '%s_refs_%s_%s' % (r_col, col,
                                  digest(connection, r_table, table))
Example #12
0
def generate_index_name(connection, table, col_names, field_names=None,
                        index_together=False, model_meta_indexes=False):
    """Generate a suitable index name to test against.

    The returned index name is meant for use in the test data modules, and
    is used to compare our own expectations of how an index should be named
    with the naming Django provides in its own functions.

    Args:
        connection (django.db.backends.base.base.BaseDatabaseWrapper):
            The database connection.

        table (unicode):
            The name of the table the index refers to.

        col_names (unicode or list of unicode):
            The column name, or list of column names, for the index.

            This is used for Postgres (when not using ``index_together``),
            or for Django < 1.5. Otherwise, it's interchangeable with
            ``field_names``.

        field_names (str or list of str, optional):
            The field name, or list of field names, for the index.

            This is interchangeable with ``column_names`` on Django >= 1.5
            (unless using Postgres without ``index_together``), or when
            passing ``default=True``.

        index_together (bool, optional):
            Whether this index covers multiple fields indexed together
            through Django's ``Model._meta.index_together``.

            Defaults to ``False``.

        model_meta_indexes (bool, optional):
            The index comes from a
            :py:class:`django.db.models.Options.indexes` entry.

    Returns:
        unicode:
        The resulting index name for the given criteria.
    """
    if not isinstance(col_names, list):
        col_names = [col_names]

    if field_names and not isinstance(field_names, list):
        field_names = [field_names]

    if not field_names:
        field_names = col_names

    assert len(field_names) == len(col_names)

    django_version = django.VERSION[:2]

    # Note that we're checking Django versions/engines specifically, since
    # we want to test that we're getting the right index names for the
    # right versions of Django, rather than asking Django for them.
    #
    # The order here matters.
    if django_version >= (1, 11):
        # Django 1.11+ changed the index format again, this time to include
        # all relevant column names in the plain text part of the index
        # (instead of just in the hash). Like with 1.7 through 1.10, the
        # index_together entries have a "_idx" suffix. However, there's
        # otherwise no difference in format between those and single-column
        # indexes.
        #
        # It's also worth noting that with the introduction of
        # Model._meta.indexes, there's *another* new index format. It's
        # similar, but different enough, and needs to be handled specially.
        if model_meta_indexes:
            name = '%s_%s' % (
                col_names[0][:7],
                digest(connection, *([table] + col_names + ['idx']))[:6],
            )
            table = table[:11]
        else:
            index_unique_name = _generate_index_unique_name_hash(
                connection, table, col_names)
            name = '%s_%s' % ('_'.join(col_names), index_unique_name)

        if model_meta_indexes or index_together:
            name = '%s_idx' % name
    elif django_version >= (1, 7):
        if len(col_names) == 1:
            assert not index_together

            # Django 1.7 went back to passing a single column name (and
            # not a list as a single variable argument) when there's only
            # one column.
            name = digest(connection, col_names[0])
        else:
            assert index_together

            index_unique_name = _generate_index_unique_name_hash(
                connection, table, col_names)
            name = '%s_%s_idx' % (col_names[0], index_unique_name)
    elif connection.vendor == 'postgresql' and not index_together:
        # Postgres computes the index names separately from the rest of
        # the engines. It just uses '<tablename>_<colname>", same as
        # Django < 1.2. We only do this for normal indexes, though, not
        # index_together.
        name = col_names[0]
    elif django_version >= (1, 5):
        # Django >= 1.5 computed the digest of the representation of a
        # list of either field names or column names. Note that digest()
        # takes variable positional arguments, which this is not passing.
        # This is due to a design bug in these versions.
        #
        # We convert each of the field names to Python's native string
        # format, which is what the default name would normally be in.
        name = digest(connection, [
            str(field_name)
            for field_name in (field_names or col_names)
        ])
    elif django_version >= (1, 2):
        # Django >= 1.2, < 1.7 used the digest of the name of the first
        # column. There was no index_together in these releases.
        name = digest(connection, col_names[0])
    else:
        # Django < 1.2 used just the name of the first column, no digest.
        name = col_names[0]

    return truncate_name('%s_%s' % (table, name),
                         connection.ops.max_name_length())