     associatedtype Right : Table
     
     var join:Join<Left, Right> {get}
+}
+
+public extension JoinProtocol {
+    public var datasets:(Left, Right) {
+        switch join {
+        case .cross(left: let left, right: let right):
+            return (left, right)
+        case .inner(left: let left, right: let right, condition: _):
+            return (left, right)
+        case .outer(left: let left, right: let right, condition: _, direction: _):
+            return (left, right)
+        }
+    }
+    
+    func replace(left _left:Left? = nil, right _right:Right? = nil) -> Join<Left, Right> {
+        switch join {
+        case .cross(left: let left, right: let right):
+            return .cross(left: _left ?? left, right: _right ?? right)
+        case .inner(left: let left, right: let right, condition: let condition):
+            return .inner(left: _left ?? left, right: _right ?? right, condition: condition)
+        case .outer(left: let left, right: let right, condition: let condition, direction: let direction):
+            return .outer(left: _left ?? left, right: _right ?? right, condition: condition, direction: direction)
+        }
+    }
 }
 
 public indirect enum Join<A : Dataset, B : Table> : Dataset, JoinProtocol {

}

public extension Query where DS : Table {
    public func map(_ f: @autoclosure (DS)->[String]) -> QueryImpl<ErasedTable> {
        let dataset = ErasedTable(name: self.dataset.name, columns: .list(f(self.dataset)))
        return QueryImpl(connection: connection,

    public init(name:String, columns:Columns = .all) {
        self.name = name
        self.columns = columns
    }
    
    public subscript(_ column:String) -> Column {
        return ErasedColumn(name: column, in: self)
    }
}

