# -*- coding: utf-8 -*-
import unittest
import sys

from nose.plugins.skip import SkipTest
from datetime import datetime
import pymongo

from mongoengine import *
from mongoengine.connection import get_db

from tests.utils import get_mongodb_version, needs_mongodb_v26

__all__ = ("IndexesTest", )


class IndexesTest(unittest.TestCase):

    def setUp(self):
        self.connection = connect(db='mongoenginetest')
        self.db = get_db()

        class Person(Document):
            name = StringField()
            age = IntField()

            non_field = True

            meta = {"allow_inheritance": True}

        self.Person = Person

    def tearDown(self):
        self.connection.drop_database(self.db)

    def test_indexes_document(self):
        """Ensure that indexes are used when meta[indexes] is specified for
        Documents
        """
        self._index_test(Document)

    def test_indexes_dynamic_document(self):
        """Ensure that indexes are used when meta[indexes] is specified for
        Dynamic Documents
        """
        self._index_test(DynamicDocument)

    def _index_test(self, InheritFrom):

        class BlogPost(InheritFrom):
            date = DateTimeField(db_field='addDate', default=datetime.now)
            category = StringField()
            tags = ListField(StringField())
            meta = {
                'indexes': [
                    '-date',
                    'tags',
                    ('category', '-date')
                ]
            }

        expected_specs = [{'fields': [('addDate', -1)]},
                          {'fields': [('tags', 1)]},
                          {'fields': [('category', 1), ('addDate', -1)]}]
        self.assertEqual(expected_specs, BlogPost._meta['index_specs'])

        BlogPost.ensure_indexes()
        info = BlogPost.objects._collection.index_information()
        # _id, '-date', 'tags', ('cat', 'date')
        self.assertEqual(len(info), 4)
        info = [value['key'] for key, value in info.items()]
        for expected in expected_specs:
            self.assertTrue(expected['fields'] in info)

    def _index_test_inheritance(self, InheritFrom):

        class BlogPost(InheritFrom):
            date = DateTimeField(db_field='addDate', default=datetime.now)
            category = StringField()
            tags = ListField(StringField())
            meta = {
                'indexes': [
                    '-date',
                    'tags',
                    ('category', '-date')
                ],
                'allow_inheritance': True
            }

        expected_specs = [{'fields': [('_cls', 1), ('addDate', -1)]},
                          {'fields': [('_cls', 1), ('tags', 1)]},
                          {'fields': [('_cls', 1), ('category', 1),
                                      ('addDate', -1)]}]
        self.assertEqual(expected_specs, BlogPost._meta['index_specs'])

        BlogPost.ensure_indexes()
        info = BlogPost.objects._collection.index_information()
        # _id, '-date', 'tags', ('cat', 'date')
        # NB: there is no index on _cls by itself, since
        # the indices on -date and tags will both contain
        # _cls as first element in the key
        self.assertEqual(len(info), 4)
        info = [value['key'] for key, value in info.items()]
        for expected in expected_specs:
            self.assertTrue(expected['fields'] in info)

        class ExtendedBlogPost(BlogPost):
            title = StringField()
            meta = {'indexes': ['title']}

        expected_specs.append({'fields': [('_cls', 1), ('title', 1)]})
        self.assertEqual(expected_specs, ExtendedBlogPost._meta['index_specs'])

        BlogPost.drop_collection()

        ExtendedBlogPost.ensure_indexes()
        info = ExtendedBlogPost.objects._collection.index_information()
        info = [value['key'] for key, value in info.items()]
        for expected in expected_specs:
            self.assertTrue(expected['fields'] in info)

    def test_indexes_document_inheritance(self):
        """Ensure that indexes are used when meta[indexes] is specified for
        Documents
        """
        self._index_test_inheritance(Document)

    def test_indexes_dynamic_document_inheritance(self):
        """Ensure that indexes are used when meta[indexes] is specified for
        Dynamic Documents
        """
        self._index_test_inheritance(DynamicDocument)

    def test_inherited_index(self):
        """Ensure index specs are inhertited correctly"""

        class A(Document):
            title = StringField()
            meta = {
                'indexes': [
                        {
                            'fields': ('title',),
                        },
                ],
                'allow_inheritance': True,
                }

        class B(A):
            description = StringField()

        self.assertEqual(A._meta['index_specs'], B._meta['index_specs'])
        self.assertEqual([{'fields': [('_cls', 1), ('title', 1)]}],
                         A._meta['index_specs'])

    def test_index_no_cls(self):
        """Ensure index specs are inhertited correctly"""

        class A(Document):
            title = StringField()
            meta = {
                'indexes': [
                        {'fields': ('title',), 'cls': False},
                ],
                'allow_inheritance': True,
                'index_cls': False
                }

        self.assertEqual([('title', 1)], A._meta['index_specs'][0]['fields'])
        A._get_collection().drop_indexes()
        A.ensure_indexes()
        info = A._get_collection().index_information()
        self.assertEqual(len(list(info.keys())), 2)

        class B(A):
            c = StringField()
            d = StringField()
            meta = {
                'indexes': [{'fields': ['c']}, {'fields': ['d'], 'cls': True}],
                'allow_inheritance': True
            }
        self.assertEqual([('c', 1)], B._meta['index_specs'][1]['fields'])
        self.assertEqual([('_cls', 1), ('d', 1)], B._meta['index_specs'][2]['fields'])

    def test_build_index_spec_is_not_destructive(self):

        class MyDoc(Document):
            keywords = StringField()

            meta = {
                'indexes': ['keywords'],
                'allow_inheritance': False
            }

        self.assertEqual(MyDoc._meta['index_specs'],
                         [{'fields': [('keywords', 1)]}])

        # Force index creation
        MyDoc.ensure_indexes()

        self.assertEqual(MyDoc._meta['index_specs'],
                        [{'fields': [('keywords', 1)]}])

    def test_embedded_document_index_meta(self):
        """Ensure that embedded document indexes are created explicitly
        """
        class Rank(EmbeddedDocument):
            title = StringField(required=True)

        class Person(Document):
            name = StringField(required=True)
            rank = EmbeddedDocumentField(Rank, required=False)

            meta = {
                'indexes': [
                    'rank.title',
                ],
                'allow_inheritance': False
            }

        self.assertEqual([{'fields': [('rank.title', 1)]}],
                        Person._meta['index_specs'])

        Person.drop_collection()

        # Indexes are lazy so use list() to perform query
        list(Person.objects)
        info = Person.objects._collection.index_information()
        info = [value['key'] for key, value in info.items()]
        self.assertTrue([('rank.title', 1)] in info)

    def test_explicit_geo2d_index(self):
        """Ensure that geo2d indexes work when created via meta[indexes]
        """
        class Place(Document):
            location = DictField()
            meta = {
                'allow_inheritance': True,
                'indexes': [
                    '*location.point',
                ]
            }

        self.assertEqual([{'fields': [('location.point', '2d')]}],
                         Place._meta['index_specs'])

        Place.ensure_indexes()
        info = Place._get_collection().index_information()
        info = [value['key'] for key, value in info.items()]
        self.assertTrue([('location.point', '2d')] in info)

    def test_explicit_geo2d_index_embedded(self):
        """Ensure that geo2d indexes work when created via meta[indexes]
        """
        class EmbeddedLocation(EmbeddedDocument):
            location = DictField()

        class Place(Document):
            current = DictField(field=EmbeddedDocumentField('EmbeddedLocation'))
            meta = {
                'allow_inheritance': True,
                'indexes': [
                    '*current.location.point',
                ]
            }

        self.assertEqual([{'fields': [('current.location.point', '2d')]}],
                         Place._meta['index_specs'])

        Place.ensure_indexes()
        info = Place._get_collection().index_information()
        info = [value['key'] for key, value in info.items()]
        self.assertTrue([('current.location.point', '2d')] in info)

    def test_explicit_geosphere_index(self):
        """Ensure that geosphere indexes work when created via meta[indexes]
        """
        class Place(Document):
            location = DictField()
            meta = {
                'allow_inheritance': True,
                'indexes': [
                    '(location.point',
                ]
            }

        self.assertEqual([{'fields': [('location.point', '2dsphere')]}],
                         Place._meta['index_specs'])

        Place.ensure_indexes()
        info = Place._get_collection().index_information()
        info = [value['key'] for key, value in info.items()]
        self.assertTrue([('location.point', '2dsphere')] in info)

    def test_explicit_geohaystack_index(self):
        """Ensure that geohaystack indexes work when created via meta[indexes]
        """
        raise SkipTest('GeoHaystack index creation is not supported for now'
                       'from meta, as it requires a bucketSize parameter.')

        class Place(Document):
            location = DictField()
            name = StringField()
            meta = {
                'indexes': [
                    (')location.point', 'name')
                ]
            }
        self.assertEqual([{'fields': [('location.point', 'geoHaystack'), ('name', 1)]}],
                         Place._meta['index_specs'])

        Place.ensure_indexes()
        info = Place._get_collection().index_information()
        info = [value['key'] for key, value in info.items()]
        self.assertTrue([('location.point', 'geoHaystack')] in info)

    def test_create_geohaystack_index(self):
        """Ensure that geohaystack indexes can be created
        """
        class Place(Document):
            location = DictField()
            name = StringField()

        Place.create_index({'fields': (')location.point', 'name')}, bucketSize=10)
        info = Place._get_collection().index_information()
        info = [value['key'] for key, value in info.items()]
        self.assertTrue([('location.point', 'geoHaystack'), ('name', 1)] in info)

    def test_dictionary_indexes(self):
        """Ensure that indexes are used when meta[indexes] contains
        dictionaries instead of lists.
        """
        class BlogPost(Document):
            date = DateTimeField(db_field='addDate', default=datetime.now)
            category = StringField()
            tags = ListField(StringField())
            meta = {
                'indexes': [
                    {'fields': ['-date'], 'unique': True, 'sparse': True},
                ],
            }

        self.assertEqual([{'fields': [('addDate', -1)], 'unique': True,
                          'sparse': True}],
                         BlogPost._meta['index_specs'])

        BlogPost.drop_collection()

        info = BlogPost.objects._collection.index_information()
        # _id, '-date'
        self.assertEqual(len(info), 2)

        # Indexes are lazy so use list() to perform query
        list(BlogPost.objects)
        info = BlogPost.objects._collection.index_information()
        info = [(value['key'],
                 value.get('unique', False),
                 value.get('sparse', False))
                for key, value in info.items()]
        self.assertTrue(([('addDate', -1)], True, True) in info)

        BlogPost.drop_collection()

    def test_abstract_index_inheritance(self):

        class UserBase(Document):
            user_guid = StringField(required=True)
            meta = {
                'abstract': True,
                'indexes': ['user_guid'],
                'allow_inheritance': True
            }

        class Person(UserBase):
            name = StringField()

            meta = {
                'indexes': ['name'],
            }
        Person.drop_collection()

        Person(name="test", user_guid='123').save()

        self.assertEqual(1, Person.objects.count())
        info = Person.objects._collection.index_information()
        self.assertEqual(sorted(info.keys()),
                         ['_cls_1_name_1', '_cls_1_user_guid_1', '_id_'])

    def test_disable_index_creation(self):
        """Tests setting auto_create_index to False on the connection will
        disable any index generation.
        """
        class User(Document):
            meta = {
                'allow_inheritance': True,
                'indexes': ['user_guid'],
                'auto_create_index': False
            }
            user_guid = StringField(required=True)

        class MongoUser(User):
            pass

        User.drop_collection()

        User(user_guid='123').save()
        MongoUser(user_guid='123').save()

        self.assertEqual(2, User.objects.count())
        info = User.objects._collection.index_information()
        self.assertEqual(list(info.keys()), ['_id_'])

        User.ensure_indexes()
        info = User.objects._collection.index_information()
        self.assertEqual(sorted(info.keys()), ['_cls_1_user_guid_1', '_id_'])

    def test_embedded_document_index(self):
        """Tests settings an index on an embedded document
        """
        class Date(EmbeddedDocument):
            year = IntField(db_field='yr')

        class BlogPost(Document):
            title = StringField()
            date = EmbeddedDocumentField(Date)

            meta = {
                'indexes': [
                    '-date.year'
                ],
            }

        BlogPost.drop_collection()

        info = BlogPost.objects._collection.index_information()
        self.assertEqual(sorted(info.keys()), ['_id_', 'date.yr_-1'])

    def test_list_embedded_document_index(self):
        """Ensure list embedded documents can be indexed
        """
        class Tag(EmbeddedDocument):
            name = StringField(db_field='tag')

        class BlogPost(Document):
            title = StringField()
            tags = ListField(EmbeddedDocumentField(Tag))

            meta = {
                'indexes': [
                    'tags.name'
                ]
            }

        BlogPost.drop_collection()

        info = BlogPost.objects._collection.index_information()
        # we don't use _cls in with list fields by default
        self.assertEqual(sorted(info.keys()), ['_id_', 'tags.tag_1'])

        post1 = BlogPost(title="Embedded Indexes tests in place",
                         tags=[Tag(name="about"), Tag(name="time")])
        post1.save()

    def test_recursive_embedded_objects_dont_break_indexes(self):

        class RecursiveObject(EmbeddedDocument):
            obj = EmbeddedDocumentField('self')

        class RecursiveDocument(Document):
            recursive_obj = EmbeddedDocumentField(RecursiveObject)
            meta = {'allow_inheritance': True}

        RecursiveDocument.ensure_indexes()
        info = RecursiveDocument._get_collection().index_information()
        self.assertEqual(sorted(info.keys()), ['_cls_1', '_id_'])

    def test_covered_index(self):
        """Ensure that covered indexes can be used
        """

        class Test(Document):
            a = IntField()
            b = IntField()

            meta = {
                'indexes': ['a'],
                'allow_inheritance': False
            }

        Test.drop_collection()

        obj = Test(a=1)
        obj.save()

        IS_MONGODB_3 = get_mongodb_version()[0] >= 3

        # Need to be explicit about covered indexes as mongoDB doesn't know if
        # the documents returned might have more keys in that here.
        query_plan = Test.objects(id=obj.id).exclude('a').explain()
        if not IS_MONGODB_3:
            self.assertFalse(query_plan['indexOnly'])
        else:
            self.assertEqual(query_plan.get('queryPlanner').get('winningPlan').get('inputStage').get('stage'), 'IDHACK')

        query_plan = Test.objects(id=obj.id).only('id').explain()
        if not IS_MONGODB_3:
            self.assertTrue(query_plan['indexOnly'])
        else:
            self.assertEqual(query_plan.get('queryPlanner').get('winningPlan').get('inputStage').get('stage'), 'IDHACK')

        query_plan = Test.objects(a=1).only('a').exclude('id').explain()
        if not IS_MONGODB_3:
            self.assertTrue(query_plan['indexOnly'])
        else:
            self.assertEqual(query_plan.get('queryPlanner').get('winningPlan').get('inputStage').get('stage'), 'IXSCAN')
            self.assertEqual(query_plan.get('queryPlanner').get('winningPlan').get('stage'), 'PROJECTION')

        query_plan = Test.objects(a=1).explain()
        if not IS_MONGODB_3:
            self.assertFalse(query_plan['indexOnly'])
        else:
            self.assertEqual(query_plan.get('queryPlanner').get('winningPlan').get('inputStage').get('stage'), 'IXSCAN')
            self.assertEqual(query_plan.get('queryPlanner').get('winningPlan').get('stage'), 'FETCH')

    def test_index_on_id(self):

        class BlogPost(Document):
            meta = {
                'indexes': [
                    ['categories', 'id']
                ]
            }

            title = StringField(required=True)
            description = StringField(required=True)
            categories = ListField()

        BlogPost.drop_collection()

        indexes = BlogPost.objects._collection.index_information()
        self.assertEqual(indexes['categories_1__id_1']['key'],
                                 [('categories', 1), ('_id', 1)])

    def test_hint(self):

        class BlogPost(Document):
            tags = ListField(StringField())
            meta = {
                'indexes': [
                    'tags',
                ],
            }

        BlogPost.drop_collection()

        for i in range(0, 10):
            tags = [("tag %i" % n) for n in range(0, i % 2)]
            BlogPost(tags=tags).save()

        self.assertEqual(BlogPost.objects.count(), 10)
        self.assertEqual(BlogPost.objects.hint().count(), 10)

        # PyMongo 3.0 bug only, works correctly with 2.X and 3.0.1+ versions
        if pymongo.version != '3.0':
            self.assertEqual(BlogPost.objects.hint([('tags', 1)]).count(), 10)

            self.assertEqual(BlogPost.objects.hint([('ZZ', 1)]).count(), 10)

        if pymongo.version >= '2.8':
            self.assertEqual(BlogPost.objects.hint('tags').count(), 10)
        else:
            def invalid_index():
                next(BlogPost.objects.hint('tags'))
            self.assertRaises(TypeError, invalid_index)

        def invalid_index_2():
            return next(BlogPost.objects.hint(('tags', 1)))
        self.assertRaises(Exception, invalid_index_2)

    def test_unique(self):
        """Ensure that uniqueness constraints are applied to fields.
        """
        class BlogPost(Document):
            title = StringField()
            slug = StringField(unique=True)

        BlogPost.drop_collection()

        post1 = BlogPost(title='test1', slug='test')
        post1.save()

        # Two posts with the same slug is not allowed
        post2 = BlogPost(title='test2', slug='test')
        self.assertRaises(NotUniqueError, post2.save)

        # Ensure backwards compatibilty for errors
        self.assertRaises(OperationError, post2.save)

    def test_unique_with(self):
        """Ensure that unique_with constraints are applied to fields.
        """
        class Date(EmbeddedDocument):
            year = IntField(db_field='yr')

        class BlogPost(Document):
            title = StringField()
            date = EmbeddedDocumentField(Date)
            slug = StringField(unique_with='date.year')

        BlogPost.drop_collection()

        post1 = BlogPost(title='test1', date=Date(year=2009), slug='test')
        post1.save()

        # day is different so won't raise exception
        post2 = BlogPost(title='test2', date=Date(year=2010), slug='test')
        post2.save()

        # Now there will be two docs with the same slug and the same day: fail
        post3 = BlogPost(title='test3', date=Date(year=2010), slug='test')
        self.assertRaises(OperationError, post3.save)

    def test_unique_embedded_document(self):
        """Ensure that uniqueness constraints are applied to fields on embedded documents.
        """
        class SubDocument(EmbeddedDocument):
            year = IntField(db_field='yr')
            slug = StringField(unique=True)

        class BlogPost(Document):
            title = StringField()
            sub = EmbeddedDocumentField(SubDocument)

        BlogPost.drop_collection()

        post1 = BlogPost(title='test1',
                         sub=SubDocument(year=2009, slug="test"))
        post1.save()

        # sub.slug is different so won't raise exception
        post2 = BlogPost(title='test2',
                         sub=SubDocument(year=2010, slug='another-slug'))
        post2.save()

        # Now there will be two docs with the same sub.slug
        post3 = BlogPost(title='test3',
                         sub=SubDocument(year=2010, slug='test'))
        self.assertRaises(NotUniqueError, post3.save)

    def test_unique_embedded_document_in_list(self):
        """
        Ensure that the uniqueness constraints are applied to fields in
        embedded documents, even when the embedded documents in in a
        list field.
        """
        class SubDocument(EmbeddedDocument):
            year = IntField(db_field='yr')
            slug = StringField(unique=True)

        class BlogPost(Document):
            title = StringField()
            subs = ListField(EmbeddedDocumentField(SubDocument))

        BlogPost.drop_collection()

        post1 = BlogPost(
            title='test1', subs=[
                SubDocument(year=2009, slug='conflict'),
                SubDocument(year=2009, slug='conflict')
            ]
        )
        post1.save()

        post2 = BlogPost(
            title='test2', subs=[SubDocument(year=2014, slug='conflict')]
        )

        self.assertRaises(NotUniqueError, post2.save)

    def test_unique_with_embedded_document_and_embedded_unique(self):
        """Ensure that uniqueness constraints are applied to fields on
        embedded documents.  And work with unique_with as well.
        """
        class SubDocument(EmbeddedDocument):
            year = IntField(db_field='yr')
            slug = StringField(unique=True)

        class BlogPost(Document):
            title = StringField(unique_with='sub.year')
            sub = EmbeddedDocumentField(SubDocument)

        BlogPost.drop_collection()

        post1 = BlogPost(title='test1',
                         sub=SubDocument(year=2009, slug="test"))
        post1.save()

        # sub.slug is different so won't raise exception
        post2 = BlogPost(title='test2',
                         sub=SubDocument(year=2010, slug='another-slug'))
        post2.save()

        # Now there will be two docs with the same sub.slug
        post3 = BlogPost(title='test3',
                         sub=SubDocument(year=2010, slug='test'))
        self.assertRaises(NotUniqueError, post3.save)

        # Now there will be two docs with the same title and year
        post3 = BlogPost(title='test1',
                         sub=SubDocument(year=2009, slug='test-1'))
        self.assertRaises(NotUniqueError, post3.save)

    def test_ttl_indexes(self):

        class Log(Document):
            created = DateTimeField(default=datetime.now)
            meta = {
                'indexes': [
                    {'fields': ['created'], 'expireAfterSeconds': 3600}
                ]
            }

        Log.drop_collection()

        # Indexes are lazy so use list() to perform query
        list(Log.objects)
        info = Log.objects._collection.index_information()
        self.assertEqual(3600,
                         info['created_1']['expireAfterSeconds'])

    def test_unique_and_indexes(self):
        """Ensure that 'unique' constraints aren't overridden by
        meta.indexes.
        """
        class Customer(Document):
            cust_id = IntField(unique=True, required=True)
            meta = {
                'indexes': ['cust_id'],
                'allow_inheritance': False,
            }

        Customer.drop_collection()
        cust = Customer(cust_id=1)
        cust.save()

        cust_dupe = Customer(cust_id=1)
        try:
            cust_dupe.save()
            raise AssertionError("We saved a dupe!")
        except NotUniqueError:
            pass

    def test_unique_and_primary(self):
        """If you set a field as primary, then unexpected behaviour can occur.
        You won't create a duplicate but you will update an existing document.
        """
        class User(Document):
            name = StringField(primary_key=True, unique=True)
            password = StringField()

        User.drop_collection()

        user = User(name='huangz', password='secret')
        user.save()

        user = User(name='huangz', password='secret2')
        user.save()

        self.assertEqual(User.objects.count(), 1)
        self.assertEqual(User.objects.get().password, 'secret2')

    def test_unique_and_primary_create(self):
        """Create a new record with a duplicate primary key
        throws an exception
        """
        class User(Document):
            name = StringField(primary_key=True)
            password = StringField()

        User.drop_collection()

        User.objects.create(name='huangz', password='secret')
        with self.assertRaises(NotUniqueError):
            User.objects.create(name='huangz', password='secret2')

        self.assertEqual(User.objects.count(), 1)
        self.assertEqual(User.objects.get().password, 'secret')

    def test_index_with_pk(self):
        """Ensure you can use `pk` as part of a query"""

        class Comment(EmbeddedDocument):
            comment_id = IntField(required=True)

        try:
            class BlogPost(Document):
                comments = EmbeddedDocumentField(Comment)
                meta = {'indexes': [
                            {'fields': ['pk', 'comments.comment_id'],
                             'unique': True}]}
        except UnboundLocalError:
            self.fail('Unbound local error at index + pk definition')

        info = BlogPost.objects._collection.index_information()
        info = [value['key'] for key, value in info.items()]
        index_item = [('_id', 1), ('comments.comment_id', 1)]
        self.assertTrue(index_item in info)

    def test_compound_key_embedded(self):

        class CompoundKey(EmbeddedDocument):
            name = StringField(required=True)
            term = StringField(required=True)

        class ReportEmbedded(Document):
            key = EmbeddedDocumentField(CompoundKey, primary_key=True)
            text = StringField()

        my_key = CompoundKey(name="n", term="ok")
        report = ReportEmbedded(text="OK", key=my_key).save()

        self.assertEqual({'text': 'OK', '_id': {'term': 'ok', 'name': 'n'}},
                         report.to_mongo())
        self.assertEqual(report, ReportEmbedded.objects.get(pk=my_key))

    def test_compound_key_dictfield(self):

        class ReportDictField(Document):
            key = DictField(primary_key=True)
            text = StringField()

        my_key = {"name": "n", "term": "ok"}
        report = ReportDictField(text="OK", key=my_key).save()

        self.assertEqual({'text': 'OK', '_id': {'term': 'ok', 'name': 'n'}},
                         report.to_mongo())

        # We can't directly call ReportDictField.objects.get(pk=my_key),
        # because dicts are unordered, and if the order in MongoDB is
        # different than the one in `my_key`, this test will fail.
        self.assertEqual(report, ReportDictField.objects.get(pk__name=my_key['name']))
        self.assertEqual(report, ReportDictField.objects.get(pk__term=my_key['term']))

    def test_string_indexes(self):

        class MyDoc(Document):
            provider_ids = DictField()
            meta = {
                "indexes": ["provider_ids.foo", "provider_ids.bar"],
            }

        info = MyDoc.objects._collection.index_information()
        info = [value['key'] for key, value in info.items()]
        self.assertTrue([('provider_ids.foo', 1)] in info)
        self.assertTrue([('provider_ids.bar', 1)] in info)

    def test_sparse_compound_indexes(self):

        class MyDoc(Document):
            provider_ids = DictField()
            meta = {
                "indexes": [{'fields': ("provider_ids.foo", "provider_ids.bar"),
                             'sparse': True}],
            }

        info = MyDoc.objects._collection.index_information()
        self.assertEqual([('provider_ids.foo', 1), ('provider_ids.bar', 1)],
                         info['provider_ids.foo_1_provider_ids.bar_1']['key'])
        self.assertTrue(info['provider_ids.foo_1_provider_ids.bar_1']['sparse'])

    @needs_mongodb_v26
    def test_text_indexes(self):
        class Book(Document):
            title = DictField()
            meta = {
                "indexes": ["$title"],
            }

        indexes = Book.objects._collection.index_information()
        self.assertTrue("title_text" in indexes)
        key = indexes["title_text"]["key"]
        self.assertTrue(('_fts', 'text') in key)

    def test_hashed_indexes(self):

        class Book(Document):
            ref_id = StringField()
            meta = {
                "indexes": ["#ref_id"],
            }

        indexes = Book.objects._collection.index_information()
        self.assertTrue("ref_id_hashed" in indexes)
        self.assertTrue(('ref_id', 'hashed') in indexes["ref_id_hashed"]["key"])

    def test_indexes_after_database_drop(self):
        """
        Test to ensure that indexes are re-created on a collection even
        after the database has been dropped.

        Issue #812
        """
        # Use a new connection and database since dropping the database could
        # cause concurrent tests to fail.
        connection = connect(db='tempdatabase',
                             alias='test_indexes_after_database_drop')

        class BlogPost(Document):
            title = StringField()
            slug = StringField(unique=True)

            meta = {'db_alias': 'test_indexes_after_database_drop'}

        try:
            BlogPost.drop_collection()

            # Create Post #1
            post1 = BlogPost(title='test1', slug='test')
            post1.save()

            # Drop the Database
            connection.drop_database('tempdatabase')

            # Re-create Post #1
            post1 = BlogPost(title='test1', slug='test')
            post1.save()

            # Create Post #2
            post2 = BlogPost(title='test2', slug='test')
            self.assertRaises(NotUniqueError, post2.save)
        finally:
            # Drop the temporary database at the end
            connection.drop_database('tempdatabase')


    def test_index_dont_send_cls_option(self):
        """
        Ensure that 'cls' option is not sent through ensureIndex. We shouldn't
        send internal MongoEngine arguments that are not a part of the index
        spec.

        This is directly related to the fact that MongoDB doesn't validate the
        options that are passed to ensureIndex. For more details, see:
        https://jira.mongodb.org/browse/SERVER-769
        """
        class TestDoc(Document):
            txt = StringField()

            meta = {
                'allow_inheritance': True,
                'indexes': [
                    {'fields': ('txt',), 'cls': False}
                ]
            }

        class TestChildDoc(TestDoc):
            txt2 = StringField()

            meta = {
                'indexes': [
                    {'fields': ('txt2',), 'cls': False}
                ]
            }

        TestDoc.drop_collection()
        TestDoc.ensure_indexes()
        TestChildDoc.ensure_indexes()

        index_info = TestDoc._get_collection().index_information()
        for key in index_info:
            del index_info[key]['v']  # drop the index version - we don't care about that here
            if 'ns' in index_info[key]:
                del index_info[key]['ns']  # drop the index namespace - we don't care about that here, MongoDB 3+
            if 'dropDups' in index_info[key]:
                del index_info[key]['dropDups']  # drop the index dropDups - it is deprecated in MongoDB 3+

        self.assertEqual(index_info, {
            'txt_1': {
                'key': [('txt', 1)],
                'background': False
            },
            '_id_': {
                'key': [('_id', 1)],
            },
            'txt2_1': {
                'key': [('txt2', 1)],
                'background': False
            },
            '_cls_1': {
                'key': [('_cls', 1)],
                'background': False,
            }
        })

    def test_compound_index_underscore_cls_not_overwritten(self):
        """
        Test that the compound index doesn't get another _cls when it is specified
        """
        class TestDoc(Document):
            shard_1 = StringField()
            txt_1 = StringField()

            meta = {
                'collection': 'test',
                'allow_inheritance': True,
                'sparse': True,
                'shard_key': 'shard_1',
                'indexes': [
                    ('shard_1', '_cls', 'txt_1'),
                ]
            }

        TestDoc.drop_collection()
        TestDoc.ensure_indexes()

        index_info = TestDoc._get_collection().index_information()
        self.assertTrue('shard_1_1__cls_1_txt_1_1' in index_info)


if __name__ == '__main__':
    unittest.main()
