Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug with unique constraint spanning many columns. #231

Open
sirf opened this issue Nov 7, 2014 · 1 comment
Open

Bug with unique constraint spanning many columns. #231

sirf opened this issue Nov 7, 2014 · 1 comment

Comments

@sirf
Copy link

sirf commented Nov 7, 2014

I've encountered a strange issue when a unique constraints is spanning multiple columns.
After violating the constraint once, subsequent attempts to persist entities that does NOT violate the constraint are also failing. Test code to replicate the issue below. I was expecting the entire test to succeed, but the last commit is failing. I suspect a bug.

UniqueTest.java

package org.batoo.jpa.core.test.foobz;

import java.sql.Connection;
import java.sql.Statement;
import java.sql.SQLException;
import java.util.Set;

import javax.persistence.Query;
import javax.sql.DataSource;

import junit.framework.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

import org.batoo.jpa.core.test.BaseCoreTest;

/**
 * @author sirf
 */
public class UniqueTest extends BaseCoreTest {

    @Test
    public void testUnique() throws Exception {
        DataSource dataSource = this.em().unwrap(DataSource.class);
        Connection c = dataSource.getConnection();
        execute(c, "CREATE SEQUENCE FooBz_pkFooBz_seq");
        execute(c, "CREATE TABLE FooBz (" +
          "pkFooBz BIGINT NOT NULL DEFAULT nextval('FooBz_pkFooBz_seq')," +
          "fkParent BIGINT NOT NULL DEFAULT currval('FooBz_pkFooBz_seq')," +
          "fbname TEXT NOT NULL, PRIMARY KEY (pkFooBz)," +
          "UNIQUE (fkParent, fbname))");
        execute(c, "ALTER TABLE FooBz ADD FOREIGN KEY (fkParent) REFERENCES FooBz (pkFooBz)");

        execute(c, "INSERT INTO FooBz (fbname) VALUES ('000000')");
        execute(c, "INSERT INTO FooBz (fbname) VALUES ('000001')");
        c.close();
        c = null;
        dataSource = null;
        this.close();

        FooBz root0 = this.getFooBz("000000");

        FooBz sub = new FooBz(root0, "sub1");
        this.begin();
        this.persist(sub);
        this.commit();
        Assert.assertNotNull(sub.getId());

        sub = new FooBz(root0, "sub2");
        this.begin();
        this.persist(sub);
        this.commit();
        Assert.assertNotNull(sub.getId());
        Assert.assertEquals(root0, sub.getParent());

        sub = new FooBz(root0, "sub3");
        this.begin();
        this.persist(sub);
        this.commit();
        Assert.assertNotNull(sub.getId());
        Assert.assertEquals(root0.getId(), sub.getParent().getId());


        FooBz root1 = this.getFooBz("000001");

        sub = new FooBz(root1, "sub1");
        this.begin();
        this.persist(sub);
        this.commit();
        Assert.assertNotNull(sub.getId());

        try {
            sub = new FooBz(root1, "sub1");
            this.begin();
            this.persist(sub);
            this.commit();
            Assert.fail("Should not happen, because Unique Constraint");
        } catch (Exception ignore) {
            // expected
            this.rollback();
        }

        sub = new FooBz(root1, "sub3");
        this.begin();
        this.persist(sub);
        this.commit(); // fail here. don't know why
        Assert.assertNotNull(sub.getId());
        Assert.assertEquals("sub3", sub.getFbname());
        this.close();
    }

    private static void execute(Connection c, String query) throws SQLException {
        Statement s = c.createStatement();
        s.execute(query);
        s.close();
    }

    private FooBz getFooBz(String fbname) throws SQLException {
        this.begin();
        Query q = this.cq("SELECT i FROM FooBz i WHERE i.parent = i.id AND i.fbname = :fbname");
        q.setParameter("fbname", fbname);
        FooBz result = (FooBz) q.getSingleResult();
        this.commit();
        return result;
    }
}

FooBz.java

package org.batoo.jpa.core.test.foobz;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

@Table(name = "FooBz",
       uniqueConstraints = @UniqueConstraint(columnNames = { "fkParent", "fbname" }))
@Entity
@SuppressWarnings("serial")
public class FooBz implements Serializable {

    @Column(name = "pkFooBz", nullable = false, unique = true, updatable = false)
    @Id
    @SequenceGenerator(allocationSize = 1, name = "FooBz_pkFooBz_seq",
                                           sequenceName = "Foo_pkFooBz_seq")
    @GeneratedValue(generator = "FooBz_pkFooBz_seq", strategy = GenerationType.SEQUENCE)
    private Long id = null;

    @JoinColumn(name = "fkParent", nullable = false)
    @ManyToOne
    private FooBz parent = null;

    @Column(name = "fbname", nullable = false)
    private String fbname = null;

    protected FooBz() {
    }

    public FooBz(FooBz parent, String fbname) {
        this.parent = parent;
        this.fbname = fbname;
    }

    public Long getId() {
        return id;
    }

    protected void setId(Long id) {
        this.id = id;
    }

    public FooBz getParent() {
        return parent;
    }

    public void setParent(FooBz parent) {
        this.parent = parent;
    }

    public String getFbname() {
        return fbname;
    }

    public void setFbname(String fbname) {
        this.fbname = fbname;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        } else if (obj instanceof FooBz) {
            final FooBz rhs = (FooBz) obj;
            return getFbname().equals(rhs.getFbname()) && getParent().equals(rhs.getParent());
        }
        return false;
    }

    @Override
    public int hashCode() {
        return getFbname().hashCode();
    }
}

persistence.xml

<persistence version="2.0"
             xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
                                 http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">

    <persistence-unit name="default">
        <provider>org.batoo.jpa.core.BatooPersistenceProvider</provider>

        <class>org.batoo.jpa.core.test.foobz.FooBz</class>

        <exclude-unlisted-classes>true</exclude-unlisted-classes>
        <properties>
            <property name="org.batoo.jpa.ddl" value="NONE" />
        </properties>
    </persistence-unit>
</persistence>
@asimarslan
Copy link
Contributor

Thanks @sirf , I will look into it. You can send the test code via pull requests so that your commit just goes into the code base.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants