Diff extends AbstractFunction implements DiffObserver { @Override public Sequence execute(final StaticContext sctx, final QueryContext ctx, final Sequence[] args) { if (args.length != 4) { throw new QueryException(new QNm("No valid arguments specified!")); } final XmlDBCollection col = (XmlDBCollection) ctx.getNodeStore().lookup(((Str) args[0]).stringValue()); if (col == null) { throw new QueryException(new QNm("No valid arguments specified!")); } final String expResName = ((Str) args[1]).stringValue(); final int rev1 = FunUtil.getInt(args, 2, "revision1", -1, null, false); final int rev2 = FunUtil.getInt(args, 3, "revision2", -1, null, false); final XmlDBNode doc = col.getDocument(expResName); diffs.clear(); buf.setLength(0); latch = new CountDownLatch(1); try (final XmlResourceManager resMrg = doc.getTrx().getResourceManager()) { pool.submit(() -> DiffFactory.invokeFullXmlDiff(new DiffFactory.Builder<>(resMrg, rev2, rev1, resMrg.getResourceConfig().hashType == HashType.NONE ? DiffOptimized.NO : DiffOptimized.HASHED, ImmutableSet.of(this)).skipSubtrees(true))); try { latch.await(100000, TimeUnit.SECONDS); } catch (InterruptedException e) { throw new QueryException(new QNm("Interrupted exception"), e); } if (diffs.size() == 1 && (diffs.get(0).getDiff() == DiffType.SAMEHASH || diffs.get(0).getDiff() == DiffType.SAME)) { return null; } final Set<Long> nodeKeysOfInserts = diffs.stream() .filter(tuple -> tuple.getDiff() == DiffType.INSERTED || tuple.getDiff() == DiffType.REPLACEDNEW) .map(DiffTuple::getNewNodeKey) .collect(Collectors.toSet()); final Set<Long> nodeKeysOfDeletes = diffs.stream() .filter(tuple -> tuple.getDiff() == DiffType.DELETED || tuple.getDiff() == DiffType.REPLACEDOLD) .map(DiffTuple::getOldNodeKey) .collect(Collectors.toSet()); buf.append("let $doc := "); createDocString(args, rev1); buf.append(System.getProperty("line.separator")); buf.append("return ("); buf.append(System.getProperty("line.separator")); try (final XmlNodeReadOnlyTrx oldRtx = resMrg.beginNodeReadOnlyTrx(rev1); final XmlNodeReadOnlyTrx newRtx = resMrg.beginNodeReadOnlyTrx(rev2)) { final Iterator<DiffTuple> iter = diffs.iterator(); while (iter.hasNext()) { final DiffTuple tuple = iter.next(); final DiffType diffType = tuple.getDiff(); if (diffType == DiffType.SAME || diffType == DiffType.SAMEHASH || diffType == DiffType.REPLACEDOLD) { iter.remove(); } else if (diffType == DiffType.INSERTED || diffType == DiffType.REPLACEDNEW) { newRtx.moveTo(tuple.getNewNodeKey()); if ((newRtx.isAttribute() || newRtx.isNamespace()) && nodeKeysOfInserts.contains(newRtx.getParentKey())) iter.remove(); } if (diffType == DiffType.DELETED) { oldRtx.moveTo(tuple.getOldNodeKey()); if ((oldRtx.isAttribute() || oldRtx.isNamespace()) && nodeKeysOfDeletes.contains(oldRtx.getParentKey())) iter.remove(); } } if (diffs.isEmpty()) return null; for (int i = 0, length = diffs.size(); i < length; i++) { final DiffTuple diffTuple = diffs.get(i); final DiffType diffType = diffTuple.getDiff(); newRtx.moveTo(diffTuple.getNewNodeKey()); oldRtx.moveTo(diffTuple.getOldNodeKey()); switch (diffType) { case INSERTED: if ((newRtx.isAttribute() || newRtx.isNameNode()) && nodeKeysOfInserts.contains(newRtx.getParentKey())) continue; if (newRtx.isAttribute()) { buf.append(" insert node " + printNode(newRtx) + " into sdb:select-item($doc"); buf.append(","); buf.append(newRtx.getParentKey()); buf.append(")"); } else { buf.append(" insert nodes "); buf.append(printSubtreeNode(newRtx)); if (oldRtx.isDocumentRoot()) { buildUpdateStatement(newRtx); } else { buildUpdateStatement(oldRtx); } } if (i + 1 < length) buf.append(","); buf.append(System.getProperty("line.separator")); break; case DELETED: if ((newRtx.isAttribute() || newRtx.isNameNode()) && nodeKeysOfInserts.contains(newRtx.getParentKey())) continue; buf.append(" delete nodes sdb:select-item($doc"); buf.append(", "); buf.append(diffTuple.getOldNodeKey()); buf.append(")"); if (i + 1 < length) buf.append(","); buf.append(System.getProperty("line.separator")); break; case REPLACEDNEW: if (newRtx.getKind() == NodeKind.ATTRIBUTE && nodeKeysOfInserts.contains(newRtx.getParentKey())) continue; buf.append(" replace node sdb:select-item($doc"); buf.append(", "); buf.append(diffTuple.getOldNodeKey()); buf.append(") with "); buf.append(printSubtreeNode(newRtx)); if (i + 1 < length) buf.append(","); buf.append(System.getProperty("line.separator")); break; case UPDATED: if (oldRtx.isText() || oldRtx.isComment()) { replaceValue(newRtx, diffTuple); } else if (oldRtx.isAttribute()) { final boolean isNameEqual = oldRtx.getName().equals(newRtx.getName()); if (!isNameEqual) { renameNode(newRtx, diffTuple); } if (!Objects.equal(oldRtx.getValue(), newRtx.getValue())) { if (!isNameEqual) { buf.append(","); buf.append(System.getProperty("line.separator")); } replaceValue(newRtx, diffTuple); } } else { renameNode(newRtx, diffTuple); } if (i + 1 < length) buf.append(","); buf.append(System.getProperty("line.separator")); default: } } } } buf.append(")"); buf.append(System.getProperty("line.separator")); return new Str(buf.toString()); } Diff(final QNm name, final Signature signature); @Override Sequence execute(final StaticContext sctx, final QueryContext ctx, final Sequence[] args); @Override void diffListener(final DiffType diffType, final long newNodeKey, final long oldNodeKey,
      final DiffDepth depth); @Override void diffDone(); final static QNm DIFF; }
@Test public void testSimpleDiff() throws Exception { final Path databasePath = PATHS.PATH2.getFile(); final DatabaseConfiguration config = new DatabaseConfiguration(databasePath); Databases.createXmlDatabase(config); try (final var database = Databases.openXmlDatabase(databasePath)) { database.createResource(ResourceConfiguration.newBuilder(XmlTestHelper.RESOURCE).build()); try (final XmlResourceManager manager = database.openResourceManager(XmlTestHelper.RESOURCE); final XmlNodeTrx wtx = manager.beginNodeTrx()) { wtx.insertSubtreeAsFirstChild(XmlShredder.createStringReader("<xml>foo<bar/></xml>")); wtx.moveTo(3); wtx.insertSubtreeAsFirstChild(XmlShredder.createStringReader("<xml>foo<bar/></xml>")); } } try (final BasicXmlDBStore store = BasicXmlDBStore.newBuilder().location(databasePath.getParent()).build()) { final QueryContext ctx = SirixQueryContext.createWithNodeStore(store); final String dbName = databasePath.getFileName().toString(); final String resName = XmlTestHelper.RESOURCE; final String xq = "xml:diff('" + dbName + "','" + resName + "',1,2)"; final XQuery query = new XQuery(SirixCompileChain.createWithNodeStore(store), xq); try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) { query.serialize(ctx, new PrintStream(out)); final String content = new String(out.toByteArray(), StandardCharsets.UTF_8); out.reset(); new XQuery(SirixCompileChain.createWithNodeStore(store), content).execute(ctx); final String xq2 = "xml:doc('" + dbName + "','" + resName + "',3)"; new XQuery(SirixCompileChain.createWithNodeStore(store), xq2).serialize(ctx, new PrintStream(out)); final String contentNewRev = new String(out.toByteArray(), StandardCharsets.UTF_8); out.reset(); final String xq3 = "xml:doc('" + dbName + "','" + resName + "',2)"; new XQuery(SirixCompileChain.createWithNodeStore(store), xq3).serialize(ctx, new PrintStream(out)); final String contentOldRev = new String(out.toByteArray(), StandardCharsets.UTF_8); Assert.assertEquals(contentNewRev, contentOldRev); out.reset(); } } }
@Test public void testMultipleDiffs() throws IOException, QueryException { final Path database = PATHS.PATH1.getFile(); try (final BasicXmlDBStore store = BasicXmlDBStore.newBuilder().location(database.getParent()).build()) { final QueryContext ctx = SirixQueryContext.createWithNodeStore(store); final String dbName = database.toString(); final String resName = XmlTestHelper.RESOURCE; final String xq1 = "xml:diff('" + dbName + "', '" + resName + "',1,5)"; final XQuery query = new XQuery(SirixCompileChain.createWithNodeStore(store), xq1); try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) { query.serialize(ctx, new PrintStream(out)); final String content = new String(out.toByteArray(), StandardCharsets.UTF_8); out.reset(); new XQuery(SirixCompileChain.createWithNodeStore(store), content).execute(ctx); final String xq2 = "xml:doc('" + dbName + "','" + resName + "',6)"; new XQuery(SirixCompileChain.createWithNodeStore(store), xq2).serialize(ctx, new PrintStream(out)); final String contentNewRev = new String(out.toByteArray(), StandardCharsets.UTF_8); out.reset(); final String xq3 = "xml:doc('" + dbName + "','" + resName + "',5)"; new XQuery(SirixCompileChain.createWithNodeStore(store), xq3).serialize(ctx, new PrintStream(out)); final String contentOldRev = new String(out.toByteArray(), StandardCharsets.UTF_8); Assert.assertEquals(contentNewRev, contentOldRev); out.reset(); } } }