Andy,

I've run into a thorny problem to do with applying transformations to custom
algebra operators.  Essentially we have a number of custom algebra operators
defined (which derived from OpExt) and in order to try and allow
optimisations to work correctly for these we override the apply() method and
create a new instance of the custom operator with the transformer applied to
the sub operators using the Transformer.transform() helper method.

This works great for bottom up transformations but for top down
transformations e.g transformation to quad form we've found this can result
in incorrect transformations.  The following code is a trivial demonstration
of this problem:

package org.apache.jena.playground;



import org.apache.jena.atlas.io.IndentedWriter;



import com.hp.hpl.jena.graph.Node;

import com.hp.hpl.jena.graph.NodeFactory;

import com.hp.hpl.jena.graph.Triple;

import com.hp.hpl.jena.sparql.algebra.AlgebraQuad;

import com.hp.hpl.jena.sparql.algebra.Op;

import com.hp.hpl.jena.sparql.algebra.Transform;

import com.hp.hpl.jena.sparql.algebra.Transformer;

import com.hp.hpl.jena.sparql.algebra.op.OpBGP;

import com.hp.hpl.jena.sparql.algebra.op.OpExt;

import com.hp.hpl.jena.sparql.algebra.op.OpGraph;

import com.hp.hpl.jena.sparql.core.BasicPattern;

import com.hp.hpl.jena.sparql.engine.ExecutionContext;

import com.hp.hpl.jena.sparql.engine.QueryIterator;

import com.hp.hpl.jena.sparql.serializer.SerializationContext;

import com.hp.hpl.jena.sparql.util.NodeIsomorphismMap;



public class CustomOperatorTransformPassing {



    public static void main(String[] args) {

        Node customGraph = NodeFactory.createURI("http://graph";);

        Node s = NodeFactory.createVariable("s");

        Node p = NodeFactory.createVariable("p");

        Node o = NodeFactory.createVariable("o");

        

        BasicPattern bp = new BasicPattern();

        bp.add(new Triple(s, p, o));

        

        OpBGP bgp = new OpBGP(bp);

        OpGraph graph = new OpGraph(customGraph, bgp);

        

        // Transform normal algebra to quads

        Op quads = AlgebraQuad.quadize(graph);

        

        System.out.println("Original Algebra:");

        System.out.println(graph.toString());

        System.out.println();

        System.out.println("Quad Form Algebra:");

        System.out.println(quads.toString());

        System.out.println();

        

        // Now wrap in custom algebra and repeat

        Op foo = new OpFoo(graph);

        quads = AlgebraQuad.quadize(foo);

        

        System.out.println("Original Algebra:");

        System.out.println(foo.toString());

        System.out.println();

        System.out.println("Quad Form Algebra:");

        System.out.println(quads.toString());

        System.out.println();

    }

    

    /**

     * Trivial custom algebra operator which tries to allow transforms to
pass

     * through it

     * 

     */

    private static class OpFoo extends OpExt {

        private Op subOp;



        public OpFoo(Op subOp) {

            super("foo");

            this.subOp = subOp;

        }



        public Op getSubOp() {

            return this.subOp;

        }



        @Override

        public Op effectiveOp() {

            return null;

        }



        @Override

        public QueryIterator eval(QueryIterator input, ExecutionContext
execCxt) {

            throw new UnsupportedOperationException();

        }



        @Override

        public Op apply(Transform transform) {

            // Apply transforms on inner operator so we don't block
transformations

            return new OpFoo(Transformer.transform(transform, this.subOp));

        }



        @Override

        public void outputArgs(IndentedWriter out, SerializationContext
sCxt) {

            // Not needed as we override output directly

        }



        @Override

        public void output(IndentedWriter out, SerializationContext sCxt) {

            out.println("(foo");

            out.incIndent();

            subOp.output(out, sCxt);

            out.decIndent();

            out.write(")");

        }



        @Override

        public int hashCode() {

            return subOp.hashCode();

        }



        @Override

        public boolean equalTo(Op other, NodeIsomorphismMap labelMap) {

            if (other instanceof OpFoo) {

                return this.subOp.equalTo(((OpFoo) other).getSubOp(),
labelMap);

            }

            return false;

        }



    }

}



This can also be found at
https://gist.github.com/rvesse/85aeac225b7907db1155 for when the mailing
list mangles the code



It produces the following output:



Original Algebra:

(graph <http://graph>

  (bgp (triple ?s ?p ?o)))





Quad Form Algebra:

(quadpattern (quad <http://graph> ?s ?p ?o))





Original Algebra:

(foo

  (graph <http://graph>

    (bgp (triple ?s ?p ?o)))

)



Quad Form Algebra:

(foo

  (quadpattern (quad <urn:x-arq:DefaultGraphNode> ?s ?p ?o))

)


Also included with the gist.

As can be seen in the above example output the transformation causes the
graph field in the resulting quadpattern operator to be incorrectly
overwritten

Under a debugger I can see that this is because when invoked via
Transformer.Transform() the quad form transform gets applied bottom up
instead of top down so the bgp is visited prior to the graph and so the
correct graph node is not honoured

I'm assuming this is essentially a user error on my part because of the way
I am blindly using Transform.transform() to apply the transform given.  Part
of the problem is that in every other case bottom up application is going to
be the desired behaviour.  Is there an alternative way to apply the
transform or to detect when a transform wants to be top down?

Any thoughts would be most appreciated,

Cheers,

Rob



Reply via email to