                 } while true
             }
             
+        case .seq(let lhs, let rhs):
+            let jvar = freshVariable()
+            guard case .variable(let jvarname, _) = jvar else { fatalError() }
+            let i = try evaluatePath(subject: subject, object: jvar, graph: graph, path: lhs)
+            let j = try evaluatePath(subject: jvar, object: object, graph: graph, path: rhs)
+            return pipelinedHashJoin(joinVariables: [jvarname], lhs: i, rhs: j)
         }
     }
     

    case inv(PropertyPath)
    case nps([Term])
    case alt(PropertyPath, PropertyPath)
}

public indirect enum Algebra {

            let iriStrings = chars.elements().map { String($0) }.joined(separator: "").components(separatedBy: " ")
            let iris = iriStrings.map { Term(value: $0, type: .iri) }
            return .path(nodes[0], .nps(iris), nodes[1])
        } else if op == "path" {
            let parser = NTriplesPatternParser(reader: "")
            let view = AnyIterator(rest.unicodeScalars.makeIterator())
            var chars = PeekableIterator(generator: view)
            guard let nodes = parser.parseNodes(chars: &chars, count: 2) else { return nil }
            guard nodes.count == 2 else { return nil }
            let rest = chars.elements().map { String($0) }.joined(separator: "").components(separatedBy: " ")
            guard let pp = try parsePropertyPath(rest) else { throw QueryError.parseError("Failed to parse property path") }
            return .path(nodes[0], pp, nodes[1])
        } else if op == "agg" { // (SUM(?skey) AS ?sresult) (AVG(?akey) AS ?aresult) ... GROUP BY ?x ?y ?z --> "agg sum sresult ?skey , avg aresult ?akey ; ?x , ?y , ?z"
            let pair = parts.suffix(from: 1).split(separator: ";")
            guard pair.count >= 1 else { throw QueryError.parseError("Bad syntax for agg operation") }
