/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.dialect;

import java.sql.BatchUpdateException;
import java.sql.CallableStatement;
import java.sql.SQLException;

import org.hibernate.JDBCException;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.PessimisticLockException;
import org.hibernate.exception.LockAcquisitionException;
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;

import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.junit.Test;

import org.mockito.Mockito;

import static junit.framework.TestCase.assertEquals;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

/**
 * Testing of patched support for PostgreSQL Lock error detection. HHH-7251
 *
 * @author Bryan Varner
 */
public class PostgreSQL81DialectTestCase extends BaseUnitTestCase {

	@Test
	public void testDeadlockException() {
		PostgreSQL81Dialect dialect = new PostgreSQL81Dialect();
		SQLExceptionConversionDelegate delegate = dialect.buildSQLExceptionConversionDelegate();
		assertNotNull(delegate);

		JDBCException exception = delegate.convert(new SQLException("Deadlock Detected", "40P01"), "", "");
		assertTrue(exception instanceof LockAcquisitionException);
	}

	@Test
	public void testTimeoutException() {
		PostgreSQL81Dialect dialect = new PostgreSQL81Dialect();
		SQLExceptionConversionDelegate delegate = dialect.buildSQLExceptionConversionDelegate();
		assertNotNull(delegate);

		JDBCException exception = delegate.convert(new SQLException("Lock Not Available", "55P03"), "", "");
		assertTrue(exception instanceof PessimisticLockException);
	}

	/**
	 * Tests that getForUpdateString(String aliases, LockOptions lockOptions) will return a String
	 * that will effect the SELECT ... FOR UPDATE OF tableAlias1, ..., tableAliasN
	 */
	@TestForIssue( jiraKey = "HHH-5654" )
	public void testGetForUpdateStringWithAliasesAndLockOptions() {
		PostgreSQL81Dialect dialect = new PostgreSQL81Dialect();
		LockOptions lockOptions = new LockOptions();
		lockOptions.setAliasSpecificLockMode("tableAlias1", LockMode.PESSIMISTIC_WRITE);

		String forUpdateClause = dialect.getForUpdateString("tableAlias1", lockOptions);
		assertTrue("for update of tableAlias1".equals(forUpdateClause));

		lockOptions.setAliasSpecificLockMode("tableAlias2", LockMode.PESSIMISTIC_WRITE);
		forUpdateClause = dialect.getForUpdateString("tableAlias1,tableAlias2", lockOptions);
		assertTrue("for update of tableAlias1,tableAlias2".equals(forUpdateClause));
	}

	@Test
	public void testExtractConstraintName() {
		PostgreSQL81Dialect dialect = new PostgreSQL81Dialect();
		SQLException psqlException = new java.sql.SQLException("ERROR: duplicate key value violates unique constraint \"uk_4bm1x2ultdmq63y3h5r3eg0ej\" Detail: Key (username, server_config)=(user, 1) already exists.", "23505");
		BatchUpdateException batchUpdateException = new BatchUpdateException("Concurrent Error", "23505", null);
		batchUpdateException.setNextException(psqlException);
		String constraintName = dialect.getViolatedConstraintNameExtracter().extractConstraintName(batchUpdateException);
		assertThat(constraintName, is("uk_4bm1x2ultdmq63y3h5r3eg0ej"));
	}

	@Test
	@TestForIssue(jiraKey = "HHH-8687")
	public void testMessageException() throws SQLException {
		PostgreSQL81Dialect dialect = new PostgreSQL81Dialect();
		try {
			dialect.getResultSet( Mockito.mock( CallableStatement.class), "abc" );
			fail( "Expected UnsupportedOperationException" );
		}
		catch (Exception e) {
			assertTrue( e instanceof UnsupportedOperationException );
			assertEquals( "PostgreSQL only supports accessing REF_CURSOR parameters by position", e.getMessage() );
		}
	}
}
