#
#  This file is part of Oracle NoSQL Database
#  Copyright (C) 2014, 2018 Oracle and/or its affiliates.  All rights reserved.
#
# If you have received this file as part of Oracle NoSQL Database the
# following applies to the work as a whole:
#
#   Oracle NoSQL Database server software is free software: you can
#   redistribute it and/or modify it under the terms of the GNU Affero
#   General Public License as published by the Free Software Foundation,
#   version 3.
#
#   Oracle NoSQL Database is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#   Affero General Public License for more details.
#
# If you have received this file as part of Oracle NoSQL Database Client or
# distributed separately the following applies:
#
#   Oracle NoSQL Database client software is free software: you can
#   redistribute it and/or modify it under the terms of the Apache License
#   as published by the Apache Software Foundation, version 2.0.
#
# You should have received a copy of the GNU Affero General Public License
# and/or the Apache License in the LICENSE file along with Oracle NoSQL
# Database client or server distribution.  If not, see
# <http://www.gnu.org/licenses/>
# or
# <http://www.apache.org/licenses/LICENSE-2.0>.
#
# An active Oracle commercial licensing agreement for this product supersedes
# these licenses and in such case the license notices, but not the copyright
# notice, may be removed by you in connection with your distribution that is
# in accordance with the commercial licensing terms.
#
# For more information please contact:
#
# berkeleydb-info_us@oracle.com
#
from decimal import Decimal
import unittest

from nosqldb import COMMIT_NO_SYNC
from nosqldb import Factory
from nosqldb import FaultException
from nosqldb import Row
from nosqldb import StoreConfig
from nosqldb import WriteOptions
from nosqldb import ONDB_DURABILITY
from nosqldb import ONDB_RETURN_CHOICE
from nosqldb import ONDB_TIMEOUT
from testSetup import add_runtime_params
from testSetup import get_kvproxy_config
from testSetup import get_kvstore_config
from testSetup import get_store
from testSetup import host_port
from testSetup import set_security


class TestIdentityValue(unittest.TestCase):


    @classmethod
    def setUpClass(cls):
        cls._store = get_store()
        table_names = ['Tid_Int', 'Tc_Int', 'Tid_Long',
                       'Tc_Long', 'Tid_Number', 'Tc_Number']
        for t in table_names:
            cls._store.execute_sync("DROP TABLE IF EXISTS " + t)
        cls._store.refresh_tables()
        cls._store.execute_sync("CREATE TABLE Tid_Int " +
                                "(id INTEGER GENERATED ALWAYS AS " +
                                "IDENTITY (START WITH 1 INCREMENT BY 10 " +
                                "MAXVALUE 101 NO CYCLE), " +
                                "name STRING, PRIMARY KEY (id))")
        cls._store.execute_sync("CREATE TABLE Tc_Int " +
                                "(id INTEGER GENERATED BY DEFAULT AS " +
                                "IDENTITY (START WITH 1 INCREMENT BY 1 " +
                                "NO CYCLE), " +
                                "name STRING, PRIMARY KEY (id))")
        cls._store.execute_sync("CREATE TABLE Tid_Long " +
                                "(id LONG GENERATED ALWAYS AS " +
                                "IDENTITY (START WITH 0 INCREMENT BY -10 " +
                                "MINVALUE -100 NO CYCLE), " +
                                "name STRING, c INTEGER, PRIMARY KEY (c))")
        cls._store.execute_sync("CREATE TABLE Tc_Long " +
                                "(id LONG GENERATED BY DEFAULT AS " +
                                "IDENTITY (START WITH 1 INCREMENT BY 1 " +
                                "NO CYCLE), " +
                                "name STRING, c INTEGER, PRIMARY KEY (c))")
        cls._store.execute_sync("CREATE TABLE Tid_Number " +
                                "(id NUMBER GENERATED ALWAYS AS " +
                                "IDENTITY (" +
                                "START WITH 123456789012345678901234567890 " +
                                "INCREMENT BY 1 " +
                                "MAXVALUE 123456789012345678901234567990 " +
                                "NO CYCLE), " +
                                "name STRING, c INTEGER, PRIMARY KEY (c))")
        cls._store.execute_sync("CREATE TABLE Tc_Number " +
                                "(id NUMBER GENERATED BY DEFAULT AS " +
                                "IDENTITY (START WITH 1 INCREMENT BY 10 " +
                                "NO CYCLE), " +
                                "name STRING, c INTEGER, PRIMARY KEY (c))")
        cls._store.refresh_tables()


    @classmethod
    def tearDownClass(cls):
        table_names = ['Tid_Int', 'Tc_Int', 'Tid_Long',
                       'Tc_Long', 'Tid_Number', 'Tc_Number']
        for t in table_names:
            cls._store.execute_sync("DROP TABLE IF EXISTS " + t)
        cls._store.refresh_tables()
        cls._store.close()

    def setUp(self):
        s_conf = get_kvstore_config()
        p_conf = get_kvproxy_config()
        set_security(s_conf, p_conf)
        s_conf.set_durability({
            'master_sync': 'SYNC',
            'replica_sync': 'NO_SYNC',
            'replica_ack': 'ALL'})
        StoreConfig.change_log('DEBUG', None)
        self.store = Factory.open(host_port, s_conf, p_conf)
        self.writeopts = WriteOptions({ONDB_DURABILITY: COMMIT_NO_SYNC,
                                  ONDB_TIMEOUT: 5000,
                                  ONDB_RETURN_CHOICE: 'ALL'})

    def tearDown(self):
        self.store.close()

    def testIdentityValueInt(self):
        # test with Tid_Int
        self.table = 'Tid_Int'
        start = 1
        incrBy = 10
        maxvalue = 101
        id = start
        while id <= maxvalue:
            namestr = 'name' + str(id)
            self.row = Row({'name': namestr})
            version, oldrow = self.store.put(self.table, self.row)
            icvalue = self.row.get_identity_value();
            self.assertEqual(icvalue, id)
            primary_key = {"id": id}
            readrow = self.store.get(self.table, primary_key)
            self.assertEqual(namestr, readrow['name'])
            id += incrBy
        self.row = Row({'name': "joe"})
        self.assertRaises(FaultException, self.store.put,
                          self.table, self.row)
        print "Got Expected Exception"
        # test with Tc_Int
        self.table = 'Tc_Int'
        maxvalue = 5
        for id in [1,2,3,4,5]:
            # case1: test put
            namestr  = 'name' + str(id)
            self.row = Row({'name': namestr})
            version, oldrow = self.store.put(self.table, self.row)
            icvalue = self.row.get_identity_value();
            self.assertEqual(icvalue, id)

            # case 2: test put_if_present with a non-exist pk
            idnotexist = -100
            namestr = 'name' + str(idnotexist)
            self.row = Row({'id': idnotexist, 'name': namestr})
            version, oldrow = self.store.put_if_present(self.table, self.row)
            primary_key = {"id": idnotexist}
            readrow = self.store.get(self.table, primary_key)
            self.assertIsNone(readrow)

            # case 3: test put_if_present with an exist pk
            namestr = 'name' + str(id) + '_put_if_present'
            self.row = Row({'id': id, 'name': namestr})
            version, oldrow = self.store.put_if_present(self.table,
                                                        self.row,
                                                        self.writeopts)
            primary_key = {"id": id}
            readrow = self.store.get(self.table, primary_key)
            self.assertEqual(readrow['name'], namestr)

            # case 4: test put_if_version
            namestr = 'name' + str(id) + '_put_if_version'
            self.row = Row({'id': id, 'name': namestr})
            retversion, orow = self.store.put_if_version(self.table,
                                                         self.row,
                                                         version,
                                                         self.writeopts)
            primary_key = {"id": id}
            readrow = self.store.get(self.table, primary_key)
            self.assertEqual(namestr, readrow['name'])
            self.assertNotEqual(retversion, version)

            # case 5 test put_if_absent with an exist pk
            oldname = namestr
            namestr = 'name' + str(id) + '_put_if_absent'
            self.row = Row({'id': id, 'name': namestr})
            version, oldrow = self.store.put_if_absent(self.table,
                                                       self.row,
                                                       self.writeopts)
            primary_key = {"id": id}
            readrow = self.store.get(self.table, primary_key)
            self.assertEqual(oldname, readrow['name'])

            # case 6 test put_if_absent with a non-exist pk
            idnew= id + maxvalue
            namestr = 'name' + str(idnew) + '_put_if_absent'
            self.row = Row({'id': idnew, 'name': namestr})
            version, oldrow = self.store.put_if_absent(self.table,
                                                       self.row,
                                                       self.writeopts)
            primary_key = {"id": idnew}
            readrow = self.store.get(self.table, primary_key)
            self.assertEqual(namestr, readrow['name'])


    def testIdentityValueLong(self):
        # test with Tid_Long
        self.table = 'Tid_Long'
        start = 0
        incrby = -10
        minvalue = -100
        id = start
        pk = 1
        while id >= minvalue:
            namestr = 'name' + str(id)
            self.row = Row({'name': namestr, 'c': pk})
            version, oldrow = self.store.put(self.table, self.row)
            icvalue = self.row.get_identity_value();
            self.assertEqual(icvalue, id)
            primary_key = {"c": pk}
            readrow = self.store.get(self.table, primary_key)
            self.assertEqual(namestr, readrow['name'])
            id += incrby
            pk += 1
        self.row = Row({'name': "joe", 'c': pk})
        self.assertRaises(FaultException, self.store.put,
                          self.table, self.row)
        print "Got Excepted Exception"
        # test with Tc_Long
        self.table = 'Tc_Long'
        maxvalue = 5
        pk = 1
        id = 1
        for pk in [1,2,3,4,5]:# id <30
            # case1: test put
            namestr  = 'name' + str(pk)
            self.row = Row({'name': namestr, 'c': pk})
            version, oldrow = self.store.put(self.table, self.row)
            icvalue = self.row.get_identity_value();
            expectedvalue = (pk - 1) * 6 + id
            self.assertEqual(icvalue, expectedvalue)

            # case 2: test put_if_present with a non-exist pk
            idnotexist = -100
            namestr = 'name' + str(idnotexist)
            self.row = Row({'c': idnotexist, 'name': namestr})
            version, oldrow = self.store.put_if_present(self.table, self.row)
            icvalue = self.row.get_identity_value();
            self.assertEqual(icvalue, expectedvalue + 1)
            primary_key = {"c": idnotexist}
            readrow = self.store.get(self.table, primary_key)
            self.assertIsNone(readrow)

            # case 3: test put_if_present with an exist pk
            namestr = 'name' + str(pk) + '_put_if_present'
            self.row = Row({'c': pk, 'name': namestr})
            version, oldrow = self.store.put_if_present(self.table,
                                                        self.row,
                                                        self.writeopts)
            icvalue = self.row.get_identity_value();
            self.assertEqual(icvalue, expectedvalue + 2)
            primary_key = {"c": pk}
            readrow = self.store.get(self.table, primary_key)
            self.assertEqual(readrow['name'], namestr)

            # case 4: test put_if_version
            namestr = 'name' + str(pk) + '_put_if_version'
            self.row = Row({'c': pk, 'name': namestr})
            retversion, orow = self.store.put_if_version(self.table,
                                                         self.row,
                                                         version,
                                                         self.writeopts)
            icvalue = self.row.get_identity_value();
            self.assertEqual(icvalue, expectedvalue + 3)
            primary_key = {"c": pk}
            readrow = self.store.get(self.table, primary_key)
            self.assertEqual(namestr, readrow['name'])
            self.assertNotEqual(retversion, version)

            # case 5 test put_if_absent with an exist pk
            oldname = namestr
            namestr = 'name' + str(pk) + '_put_if_absent'
            self.row = Row({'c': pk, 'name': namestr})
            version, oldrow = self.store.put_if_absent(self.table,
                                                       self.row,
                                                       self.writeopts)
            icvalue = self.row.get_identity_value();
            self.assertEqual(icvalue, expectedvalue + 4)
            primary_key = {"c": pk}
            readrow = self.store.get(self.table, primary_key)
            self.assertEqual(oldname, readrow['name'])

            # case 6 test put_if_absent with a non-exist pk
            idnew= id + maxvalue
            namestr = 'name' + str(idnew) + '_put_if_absent'
            self.row = Row({'c': idnew, 'name': namestr})
            version, oldrow = self.store.put_if_absent(self.table,
                                                       self.row,
                                                       self.writeopts)
            icvalue = self.row.get_identity_value();
            self.assertEqual(icvalue, expectedvalue + 5)
            primary_key = {"c": idnew}
            readrow = self.store.get(self.table, primary_key)
            self.assertEqual(namestr, readrow['name'])


    def testIdentityValueNumber(self):
        # test with Tid_Number
        self.table = 'Tid_Number'
        start = 123456789012345678901234567890
        incrby = 1
        maxvalue = 123456789012345678901234567990
        id = start
        pk = 1
        while id <= maxvalue:
            namestr = 'name' + str(id)
            self.row = Row({'name': namestr, 'c': pk})
            version, oldrow = self.store.put(self.table, self.row)
            icvalue = self.row.get_identity_value();
            self.assertEqual(icvalue, id)
            primary_key = {"c": pk}
            readrow = self.store.get(self.table, primary_key)
            self.assertEqual(namestr, readrow['name'])
            id += incrby
            pk += 1
        self.row = Row({'name': "joe", 'c': pk})
        self.assertRaises(FaultException, self.store.put,
                          self.table, self.row)
        print "Got Excepted Exception"
        # test with Tc_Number
        self.table = 'Tc_Number'
        maxvalue = 5
        pk = 1
        id = 1
        for pk in [1, 2, 3, 4, 5]:  # id <30
            # case1: test put
            namestr = 'name' + str(pk)
            self.row = Row({'name': namestr, 'c': pk})
            version, oldrow = self.store.put(self.table, self.row)
            expectedvalue = (pk - 1) * 60 + id
            icvalue = self.row.get_identity_value();
            self.assertEqual(icvalue, expectedvalue)

            # case 2: test put_if_present with a non-exist pk
            idnotexist = -100
            namestr = 'name' + str(idnotexist)
            self.row = Row({'c': idnotexist, 'name': namestr})
            version, oldrow = self.store.put_if_present(self.table, self.row)
            icvalue = self.row.get_identity_value();
            self.assertEqual(icvalue, expectedvalue + 10)
            primary_key = {"c": idnotexist}
            readrow = self.store.get(self.table, primary_key)
            self.assertIsNone(readrow)

            # case 3: test put_if_present with an exist pk
            namestr = 'name' + str(pk) + '_put_if_present'
            self.row = Row({'c': pk, 'name': namestr})
            version, oldrow = self.store.put_if_present(self.table,
                                                        self.row,
                                                        self.writeopts)
            icvalue = self.row.get_identity_value();
            self.assertEqual(icvalue, expectedvalue + 20)
            primary_key = {"c": pk}
            readrow = self.store.get(self.table, primary_key)
            self.assertEqual(readrow['name'], namestr)

            # case 4: test put_if_version
            namestr = 'name' + str(pk) + '_put_if_version'
            self.row = Row({'c': pk, 'name': namestr})
            retversion, orow = self.store.put_if_version(self.table,
                                                         self.row,
                                                         version,
                                                         self.writeopts)
            icvalue = self.row.get_identity_value();
            self.assertEqual(icvalue, expectedvalue + 30)
            primary_key = {"c": pk}
            readrow = self.store.get(self.table, primary_key)
            self.assertEqual(namestr, readrow['name'])
            self.assertNotEqual(retversion, version)

            # case 5 test put_if_absent with an exist pk
            oldname = namestr
            namestr = 'name' + str(pk) + '_put_if_absent'
            self.row = Row({'c': pk, 'name': namestr})
            version, oldrow = self.store.put_if_absent(self.table,
                                                       self.row,
                                                       self.writeopts)
            icvalue = self.row.get_identity_value();
            self.assertEqual(icvalue, expectedvalue + 40)
            primary_key = {"c": pk}
            readrow = self.store.get(self.table, primary_key)
            self.assertEqual(oldname, readrow['name'])

            # case 6 test put_if_absent with a non-exist pk
            idnew = id + maxvalue
            namestr = 'name' + str(idnew) + '_put_if_absent'
            self.row = Row({'c': idnew, 'name': namestr})
            version, oldrow = self.store.put_if_absent(self.table,
                                                       self.row,
                                                       self.writeopts)
            icvalue = self.row.get_identity_value();
            self.assertEqual(icvalue, expectedvalue + 50)
            primary_key = {"c": idnew}
            readrow = self.store.get(self.table, primary_key)
            self.assertEqual(namestr, readrow['name'])

        pk = 100
        namestr = 'name' + str(pk)
        self.row = Row({'c': pk, 'name': namestr,
                        'id': Decimal('1.234567890123456789E550')})
        retversion, oldrow = self.store.put_if_absent(self.table,
                                                      self.row,
                                                      self.writeopts)
        icvalue = self.row.get_identity_value();
        self.assertEqual(icvalue, Decimal('1.234567890123456789E550'))


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