|
18 | 18 | */ |
19 | 19 | package org.apache.tinkerpop.gremlin.tinkergraph.structure; |
20 | 20 |
|
| 21 | +import java.util.List; |
21 | 22 | import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; |
22 | 23 | import org.apache.tinkerpop.gremlin.structure.Edge; |
23 | 24 | import org.apache.tinkerpop.gremlin.structure.T; |
24 | 25 | import org.apache.tinkerpop.gremlin.structure.Vertex; |
| 26 | +import org.apache.tinkerpop.gremlin.structure.VertexProperty; |
25 | 27 | import org.apache.tinkerpop.gremlin.structure.util.TransactionException; |
26 | 28 | import org.junit.Test; |
27 | 29 |
|
|
33 | 35 |
|
34 | 36 | import static org.junit.Assert.assertEquals; |
35 | 37 | import static org.junit.Assert.assertFalse; |
| 38 | +import static org.junit.Assert.assertNotEquals; |
36 | 39 | import static org.junit.Assert.assertNotNull; |
37 | 40 | import static org.junit.Assert.assertNull; |
38 | 41 | import static org.junit.Assert.assertTrue; |
39 | 42 | import static org.junit.Assert.fail; |
40 | 43 |
|
41 | 44 | public class TinkerTransactionGraphTest { |
42 | 45 |
|
| 46 | + private static final String PROPERTY_NAME = "name"; |
43 | 47 | final Object vid = 100; |
44 | 48 |
|
45 | 49 | ///// vertex tests |
@@ -1503,4 +1507,245 @@ public void shouldHandleCorrectlyHandleCountForChangedAndReadElement() { |
1503 | 1507 |
|
1504 | 1508 | assertFalse(vertex.inUse()); |
1505 | 1509 | } |
| 1510 | + |
| 1511 | + @Test |
| 1512 | + public void shouldAllowDropAddVertexInSameTransaction() { |
| 1513 | + final TinkerTransactionGraph g = TinkerTransactionGraph.open(); |
| 1514 | + final TinkerTransaction tx = (TinkerTransaction) g.tx(); |
| 1515 | + |
| 1516 | + // create initial vertex with a property |
| 1517 | + final GraphTraversalSource gtx = tx.begin(); |
| 1518 | + Vertex vertex = gtx.addV().property(T.id, vid).property(PROPERTY_NAME, "test").next(); |
| 1519 | + // add self-referencing edge |
| 1520 | + gtx.addE("tests").from(vertex).to(vertex).next(); |
| 1521 | + gtx.tx().commit(); |
| 1522 | + |
| 1523 | + verifyCommittedSingleVertex(g, "test"); |
| 1524 | + assertEquals(1, g.getEdgesCount()); |
| 1525 | + final long originalVersion = g.getVertices().get(vid).getUnmodified().version(); |
| 1526 | + |
| 1527 | + // drop the vertex and re-create it without any properties or edges in the same transaction |
| 1528 | + final GraphTraversalSource gtx2 = tx.begin(); |
| 1529 | + gtx2.V().drop().iterate(); |
| 1530 | + gtx2.addV().property(T.id, vid).next(); |
| 1531 | + gtx2.tx().commit(); |
| 1532 | + |
| 1533 | + verifyCommittedSingleVertex(g); |
| 1534 | + final TinkerVertex updatedVertex = g.getVertices().get(vid).getUnmodified(); |
| 1535 | + assertEquals(0, g.getEdgesCount()); |
| 1536 | + // version should have been updated |
| 1537 | + assertNotEquals(originalVersion, updatedVertex.version()); |
| 1538 | + } |
| 1539 | + |
| 1540 | + @Test |
| 1541 | + public void shouldAllowAddDropAddVertexInSameTransaction() { |
| 1542 | + final TinkerTransactionGraph g = TinkerTransactionGraph.open(); |
| 1543 | + final TinkerTransaction tx = (TinkerTransaction) g.tx(); |
| 1544 | + |
| 1545 | + final GraphTraversalSource gtx = tx.begin(); |
| 1546 | + // create vertex with a property |
| 1547 | + final Vertex vertex = gtx.addV().property(T.id, vid).property(PROPERTY_NAME, "test").next(); |
| 1548 | + // add self-referencing edge |
| 1549 | + gtx.addE("tests").from(vertex).to(vertex).next(); |
| 1550 | + // drop vertex |
| 1551 | + gtx.V(vid).drop().iterate(); |
| 1552 | + // add vertex again with different properties but no edges |
| 1553 | + gtx.addV().property(T.id, vid).property(PROPERTY_NAME, "updated name").next(); |
| 1554 | + gtx.tx().commit(); |
| 1555 | + |
| 1556 | + verifyCommittedSingleVertex(g, "updated name"); |
| 1557 | + assertEquals(0, g.getEdgesCount()); |
| 1558 | + } |
| 1559 | + |
| 1560 | + @Test |
| 1561 | + public void shouldAllowAddDropAddDropVertexInSameTransaction() { |
| 1562 | + final TinkerTransactionGraph g = TinkerTransactionGraph.open(); |
| 1563 | + final TinkerTransaction tx = (TinkerTransaction) g.tx(); |
| 1564 | + |
| 1565 | + final GraphTraversalSource gtx = tx.begin(); |
| 1566 | + // create vertex with a property |
| 1567 | + final Vertex vertex = gtx.addV().property(T.id, vid).property(PROPERTY_NAME, "test").next(); |
| 1568 | + // add self-referencing edge |
| 1569 | + gtx.addE("tests").from(vertex).to(vertex).next(); |
| 1570 | + // drop vertex |
| 1571 | + gtx.V(vid).drop().iterate(); |
| 1572 | + // add vertex again with different properties but no edges |
| 1573 | + gtx.addV().property(T.id, vid).property(PROPERTY_NAME, "updated name").next(); |
| 1574 | + // drop the vertex again |
| 1575 | + gtx.V(vid).drop().iterate(); |
| 1576 | + gtx.tx().commit(); |
| 1577 | + |
| 1578 | + assertEquals(0, g.getVerticesCount()); |
| 1579 | + assertEquals(0, g.getEdgesCount()); |
| 1580 | + } |
| 1581 | + |
| 1582 | + @Test |
| 1583 | + public void shouldAllowAddDropAddDropVertexInMultipleTransactions() { |
| 1584 | + final TinkerTransactionGraph g = TinkerTransactionGraph.open(); |
| 1585 | + final TinkerTransaction tx = (TinkerTransaction) g.tx(); |
| 1586 | + |
| 1587 | + // first transaction |
| 1588 | + final GraphTraversalSource gtx1 = tx.begin(); |
| 1589 | + // create vertex with a property |
| 1590 | + final Vertex vertex = gtx1.addV().property(T.id, vid).property(PROPERTY_NAME, "test").next(); |
| 1591 | + // add self-referencing edge |
| 1592 | + gtx1.addE("tests").from(vertex).to(vertex).next(); |
| 1593 | + // drop vertex |
| 1594 | + gtx1.V(vid).drop().iterate(); |
| 1595 | + gtx1.tx().commit(); |
| 1596 | + |
| 1597 | + assertEquals(0, g.getVerticesCount()); |
| 1598 | + assertEquals(0, g.getEdgesCount()); |
| 1599 | + |
| 1600 | + // second transaction |
| 1601 | + final GraphTraversalSource gtx2 = tx.begin(); |
| 1602 | + // add vertex again with different properties but no edges |
| 1603 | + gtx2.addV().property(T.id, vid).property(PROPERTY_NAME, "updated name").next(); |
| 1604 | + // drop the vertex again |
| 1605 | + gtx2.V(vid).drop().iterate(); |
| 1606 | + gtx2.tx().commit(); |
| 1607 | + |
| 1608 | + assertEquals(0, g.getVerticesCount()); |
| 1609 | + assertEquals(0, g.getEdgesCount()); |
| 1610 | + } |
| 1611 | + |
| 1612 | + @Test |
| 1613 | + public void shouldAllowDropAddUniqueVertexInConcurrentTransactions() { |
| 1614 | + final TinkerTransactionGraph g = TinkerTransactionGraph.open(); |
| 1615 | + final TinkerTransaction tx = (TinkerTransaction) g.tx(); |
| 1616 | + |
| 1617 | + // create first vertex and commit |
| 1618 | + final GraphTraversalSource gtx = tx.begin(); |
| 1619 | + gtx.addV().property(T.id, vid).next(); |
| 1620 | + gtx.tx().commit(); |
| 1621 | + verifyCommittedSingleVertex(g); |
| 1622 | + |
| 1623 | + // drop and add but don't commit |
| 1624 | + gtx.V(vid).drop().iterate(); |
| 1625 | + gtx.addV().property(T.id, vid).property(PROPERTY_NAME, "main thread").next(); |
| 1626 | + |
| 1627 | + // in another thread add, drop, add a different vertex and commit |
| 1628 | + int vid2 = (int) vid + 1; |
| 1629 | + runInNewThread(() -> { |
| 1630 | + final GraphTraversalSource gtx2 = g.tx().begin(); |
| 1631 | + gtx2.addV().property(T.id, vid2).next(); |
| 1632 | + gtx2.V(vid2).drop().iterate(); |
| 1633 | + gtx2.addV().property(T.id, vid2).property(PROPERTY_NAME, "another thread").next(); |
| 1634 | + gtx2.tx().commit(); |
| 1635 | + }); |
| 1636 | + |
| 1637 | + // commit the drop add |
| 1638 | + gtx.tx().commit(); |
| 1639 | + // both vertices should exist |
| 1640 | + assertEquals(2, g.getVerticesCount()); |
| 1641 | + assertEquals("main thread", g.getVertices().get(vid).getUnmodified().properties.get(PROPERTY_NAME).get(0).value()); |
| 1642 | + assertEquals("another thread", g.getVertices().get(vid2).getUnmodified().properties.get(PROPERTY_NAME).get(0).value()); |
| 1643 | + } |
| 1644 | + |
| 1645 | + @Test |
| 1646 | + public void shouldPreventDropAddVertexInConcurrentTransactions() { |
| 1647 | + final TinkerTransactionGraph g = TinkerTransactionGraph.open(); |
| 1648 | + final TinkerTransaction tx = (TinkerTransaction) g.tx(); |
| 1649 | + |
| 1650 | + // create initial vertex and commit transaction |
| 1651 | + final GraphTraversalSource gtx = tx.begin(); |
| 1652 | + gtx.addV().property(T.id, vid).property(PROPERTY_NAME, "original").next(); |
| 1653 | + gtx.tx().commit(); |
| 1654 | + verifyCommittedSingleVertex(g, "original"); |
| 1655 | + |
| 1656 | + // drop the vertex and add it back but don't commit |
| 1657 | + gtx.V(vid).drop().iterate(); |
| 1658 | + gtx.addV().property(T.id, vid).property(PROPERTY_NAME, "should fail!").next(); |
| 1659 | + |
| 1660 | + // drop the vertex and add it back in another thread and commit |
| 1661 | + runInNewThread(() -> { |
| 1662 | + final GraphTraversalSource gtx2 = g.tx().begin(); |
| 1663 | + gtx2.V(vid).drop().iterate(); |
| 1664 | + gtx2.addV().property(T.id, vid).property(PROPERTY_NAME, "updated").next(); |
| 1665 | + gtx2.tx().commit(); |
| 1666 | + }); |
| 1667 | + |
| 1668 | + // try to commit the drop and add in main thread - should fail |
| 1669 | + try { |
| 1670 | + gtx.tx().commit(); |
| 1671 | + fail("should throw TransactionException"); |
| 1672 | + } catch (TransactionException e) { |
| 1673 | + assertEquals("Conflict: element modified in another transaction", e.getMessage()); |
| 1674 | + verifyCommittedSingleVertex(g, "updated"); |
| 1675 | + } |
| 1676 | + } |
| 1677 | + |
| 1678 | + @Test |
| 1679 | + public void shouldPreventDropUpdateVertexInConcurrentTransactions() { |
| 1680 | + final TinkerTransactionGraph g = TinkerTransactionGraph.open(); |
| 1681 | + final TinkerTransaction tx = (TinkerTransaction) g.tx(); |
| 1682 | + |
| 1683 | + // create initial vertex and commit transaction |
| 1684 | + final GraphTraversalSource gtx = tx.begin(); |
| 1685 | + gtx.addV().property(T.id, vid).property(PROPERTY_NAME, "original").next(); |
| 1686 | + gtx.tx().commit(); |
| 1687 | + verifyCommittedSingleVertex(g, "original"); |
| 1688 | + |
| 1689 | + // drop but don't commit |
| 1690 | + gtx.V(vid).drop().iterate(); |
| 1691 | + |
| 1692 | + // modify the vertex in another thread and commit |
| 1693 | + runInNewThread(() -> { |
| 1694 | + final GraphTraversalSource gtx2 = g.tx().begin(); |
| 1695 | + gtx2.V(vid).property(PROPERTY_NAME, "updated").next(); |
| 1696 | + gtx2.tx().commit(); |
| 1697 | + }); |
| 1698 | + |
| 1699 | + // try to commit the drop vertex - should fail |
| 1700 | + try { |
| 1701 | + gtx.tx().commit(); |
| 1702 | + fail("should throw TransactionException"); |
| 1703 | + } catch (TransactionException e) { |
| 1704 | + assertEquals("Conflict: element modified in another transaction", e.getMessage()); |
| 1705 | + verifyCommittedSingleVertex(g, "updated"); |
| 1706 | + } |
| 1707 | + } |
| 1708 | + |
| 1709 | + private void runInNewThread(final Runnable runnable) { |
| 1710 | + final Thread thread = new Thread(runnable); |
| 1711 | + thread.start(); |
| 1712 | + try { |
| 1713 | + thread.join(); |
| 1714 | + } catch (InterruptedException e) { |
| 1715 | + throw new RuntimeException(e); |
| 1716 | + } |
| 1717 | + } |
| 1718 | + |
| 1719 | + private void verifyCommittedSingleVertex(final TinkerTransactionGraph g) { |
| 1720 | + verifyCommittedSingleVertex(g, null); |
| 1721 | + } |
| 1722 | + |
| 1723 | + private void verifyCommittedSingleVertex(final TinkerTransactionGraph g, final String namePropertyValue) { |
| 1724 | + // graph should only have a single vertex |
| 1725 | + assertEquals(1, g.getVertices().size()); |
| 1726 | + |
| 1727 | + // the single vertex should have the expected id |
| 1728 | + final TinkerElementContainer<TinkerVertex> vertexContainer = g.getVertices().get(vid); |
| 1729 | + |
| 1730 | + // container should be committed and not have an update in progress |
| 1731 | + assertFalse(vertexContainer.isChanged()); |
| 1732 | + assertFalse(vertexContainer.inUse()); |
| 1733 | + |
| 1734 | + // the element in the container should have the same id |
| 1735 | + // there are multiple ways to obtain the container element's id and they should be consistent |
| 1736 | + assertEquals(vid, vertexContainer.getElementId()); |
| 1737 | + assertEquals(vid, vertexContainer.get().id()); |
| 1738 | + final TinkerVertex unmodified = vertexContainer.getUnmodified(); |
| 1739 | + assertEquals(vid, unmodified.id()); |
| 1740 | + |
| 1741 | + if (namePropertyValue != null) { |
| 1742 | + final Map<String, List<VertexProperty>> properties = g.getVertices().get(vid).getUnmodified().properties; |
| 1743 | + assertEquals(1, properties.size()); |
| 1744 | + final List<VertexProperty> nameProperties = properties.get(PROPERTY_NAME); |
| 1745 | + assertEquals(1, nameProperties.size()); |
| 1746 | + assertEquals(namePropertyValue, nameProperties.get(0).value()); |
| 1747 | + } else { |
| 1748 | + assertNull(vertexContainer.getUnmodified().properties); |
| 1749 | + } |
| 1750 | + } |
1506 | 1751 | } |
0 commit comments