/// <summary> /// Creates a new Filter Pattern with the given Filter /// </summary> /// <param name="filter">Filter</param> public FilterPattern(ISparqlFilter filter) { this._filter = filter; this._indexType = TripleIndexType.SpecialFilter; this._vars = filter.Variables.Distinct().ToList(); this._vars.Sort(); }
/// <summary> /// Creates a new Graph Pattern copied from an existing Graph Pattern /// </summary> /// <param name="gp">Graph Pattern</param> internal GraphPattern(GraphPattern gp) { _break = gp._break; _broken = gp._broken; _filter = gp._filter; _graphPatterns.AddRange(gp._graphPatterns); _graphSpecifier = gp._graphSpecifier; _isExists = gp._isExists; _isFiltered = gp._isFiltered; _isGraph = gp._isGraph; _isMinus = gp._isMinus; _isNotExists = gp._isExists; _isOptional = gp._isOptional; _isService = gp._isService; _isSilent = gp._isSilent; _isUnion = gp._isUnion; // Copy Triple Patterns across // Assignments and Filters are copied into the unplaced lists so the new pattern can be reoptimised if it gets modified since // reoptimising a pattern with already placed filters and assignments can lead to strange results _triplePatterns.AddRange(gp._triplePatterns.Where(tp => tp.PatternType != TriplePatternType.BindAssignment && tp.PatternType != TriplePatternType.LetAssignment && tp.PatternType != TriplePatternType.Filter)); _unplacedAssignments.AddRange(gp._unplacedAssignments); _unplacedAssignments.AddRange(gp._triplePatterns.Where(tp => tp.PatternType == TriplePatternType.BindAssignment || tp.PatternType == TriplePatternType.LetAssignment).OfType <IAssignmentPattern>()); _unplacedFilters.AddRange(gp._unplacedFilters); _unplacedFilters.AddRange(gp._triplePatterns.Where(tp => tp.PatternType == TriplePatternType.Filter).OfType <IFilterPattern>().Select(fp => fp.Filter)); }
/// <summary> /// Creates a new Filter Pattern with the given Filter /// </summary> /// <param name="filter">Filter</param> public FilterPattern(ISparqlFilter filter) { this._filter = filter; this._indexType = TripleIndexType.SpecialFilter; this._vars = filter.Variables.Distinct().ToList(); this._vars.Sort(); }
/// <summary> /// Inserts a Filter at a given position /// </summary> /// <param name="filter">Filter</param> /// <param name="i">Position to insert at</param> /// <remarks> /// Intended for use by Query Optimisers /// </remarks> public void InsertFilter(ISparqlFilter filter, int i) { if (!_unplacedFilters.Contains(filter)) { throw new RdfQueryException("Cannot Insert a Filter that is not currentlyy an unplaced Filter in this Graph Pattern"); } _unplacedFilters.Remove(filter); FilterPattern p = new FilterPattern(filter); _triplePatterns.Insert(i, p); }
internal GraphPattern(GraphPattern gp) { this._break = gp._break; this._broken = gp._broken; this._filter = gp._filter; this._graphPatterns.AddRange(gp._graphPatterns); this._graphSpecifier = gp._graphSpecifier; this._isExists = gp._isExists; this._isFiltered = gp._isFiltered; this._isGraph = gp._isGraph; this._isMinus = gp._isMinus; this._isNotExists = gp._isExists; this._isOptimised = gp._isOptimised; this._isOptional = gp._isOptional; this._isService = gp._isService; this._isSilent = gp._isSilent; this._isUnion = gp._isUnion; this._triplePatterns.AddRange(gp._triplePatterns); this._unplacedAssignments.AddRange(gp._unplacedAssignments); this._unplacedFilters.AddRange(gp._unplacedFilters); }
internal GraphPattern(GraphPattern gp) { this._break = gp._break; this._broken = gp._broken; this._filter = gp._filter; this._graphPatterns.AddRange(gp._graphPatterns); this._graphSpecifier = gp._graphSpecifier; this._isExists = gp._isExists; this._isFiltered = gp._isFiltered; this._isGraph = gp._isGraph; this._isMinus = gp._isMinus; this._isNotExists = gp._isExists; this._isOptimised = gp._isOptimised; this._isOptional = gp._isOptional; this._isService = gp._isService; this._isSilent = gp._isSilent; this._isUnion = gp._isUnion; this._triplePatterns.AddRange(gp._triplePatterns); this._unplacedAssignments.AddRange(gp._unplacedAssignments); this._unplacedFilters.AddRange(gp._unplacedFilters); }
/// <summary> /// Tries to place filters at the earliest point possible i.e. the first point after which all required variables have occurred /// </summary> /// <param name="gp">Graph Pattern</param> /// <param name="filter">Filter to place</param> /// <returns></returns> private bool TryPlaceFilter(GraphPattern gp, ISparqlFilter filter) { // Firstly we need to find out what variables are needed in the Filter List <String> variablesNeeded = filter.Variables.Distinct().ToList(); // Then we need to move through the Triple Patterns and find the first place at which all the // Variables used in the Filter have been used in ordinary Triple Patterns List <String> variablesUsed = new List <string>(); for (int p = 0; p < gp.TriplePatterns.Count; p++) { if (gp.TriplePatterns[p].PatternType == TriplePatternType.Match || gp.TriplePatterns[p].PatternType == TriplePatternType.BindAssignment || gp.TriplePatterns[p].PatternType == TriplePatternType.LetAssignment) { foreach (String var in gp.TriplePatterns[p].Variables) { if (!variablesUsed.Contains(var)) { variablesUsed.Add(var); } } // Have all the Variables we need now been used in a Pattern? if (variablesNeeded.All(v => variablesUsed.Contains(v))) { // We can place this Filter after the Pattern we were just looking at gp.InsertFilter(filter, p + 1); return(true); } } } // If we reach here then this means that all the Variables used in the Filter did not occur // in the Triple Patterns which means they likely occur in child graph patterns (or the query // is malformed). In this case we cannot place the Filter and it has to be applied post-commit // rather than during Triple Pattern execution return(false); }
/// <summary> /// Creates a new LeftJoin where there is a Filter over the join /// </summary> /// <param name="lhs">LHS Pattern</param> /// <param name="rhs">RHS Pattern</param> /// <param name="filter">Filter to decide which RHS solutions are valid</param> public LeftJoin(ISparqlAlgebra lhs, ISparqlAlgebra rhs, ISparqlFilter filter) { this._lhs = lhs; this._rhs = rhs; this._filter = filter; }
/// <summary> /// Adds a Filter to the Graph Pattern /// </summary> /// <param name="filter">Filter</param> internal void AddFilter(ISparqlFilter filter) { _isFiltered = true; _unplacedFilters.Add(filter); }
/// <summary> /// Creates a new Single Value Restriction Filter /// </summary> /// <param name="pattern">Algebra the filter applies over</param> /// <param name="var">Variable to restrict on</param> /// <param name="term">Value to restrict to</param> /// <param name="filter">Filter to use</param> public SingleValueRestrictionFilter(ISparqlAlgebra pattern, String var, ConstantTerm term, ISparqlFilter filter) : base(pattern, var, filter) { this._term = term; }
/// <summary> /// Creates a new Single Value Restriction Filter /// </summary> /// <param name="pattern">Algebra the filter applies over</param> /// <param name="var">Variable to restrict on</param> /// <param name="term">Value to restrict to</param> /// <param name="filter">Filter to use</param> public SingleValueRestrictionFilter(ISparqlAlgebra pattern, String var, NodeExpressionTerm term, ISparqlFilter filter) : base(pattern, var, filter) { this._term = term; }
/// <summary> /// Creates a new Single Value Restriction Filter. /// </summary> /// <param name="pattern">Algebra the filter applies over.</param> /// <param name="var">Variable to restrict on.</param> /// <param name="term">Value to restrict to.</param> /// <param name="filter">Filter to use.</param> public SingleValueRestrictionFilter(ISparqlAlgebra pattern, String var, ConstantTerm term, ISparqlFilter filter) : base(pattern, var, filter) { _term = term; }
private BaseMultiset StreamingEvaluate(SparqlEvaluationContext context, int pattern, out bool halt) { halt = false; //Handle Empty BGPs if (pattern == 0 && this._triplePatterns.Count == 0) { context.OutputMultiset = new IdentityMultiset(); return(context.OutputMultiset); } BaseMultiset initialInput, localOutput, results; //Set up the Input and Output Multiset appropriately switch (pattern) { case 0: //Input is as given and Output is new empty multiset initialInput = context.InputMultiset; localOutput = new Multiset(); break; case 1: //Input becomes current Output and Output is new empty multiset initialInput = context.OutputMultiset; localOutput = new Multiset(); break; default: //Input is join of previous input and ouput and Output is new empty multiset if (context.InputMultiset.IsDisjointWith(context.OutputMultiset)) { //Disjoint so do a Product initialInput = context.InputMultiset.Product(context.OutputMultiset); } else { //Normal Join initialInput = context.InputMultiset.Join(context.OutputMultiset); } localOutput = new Multiset(); break; } context.InputMultiset = initialInput; context.OutputMultiset = localOutput; //Get the Triple Pattern we're evaluating ITriplePattern temp = this._triplePatterns[pattern]; int resultsFound = 0; if (temp.PatternType == TriplePatternType.Match) { //Find the first Triple which matches the Pattern IMatchTriplePattern tp = (IMatchTriplePattern)temp; foreach (Triple t in tp.GetTriples(context)) { //Remember to check for Timeout during lazy evaluation context.CheckTimeout(); if (tp.Accepts(context, t)) { resultsFound++; context.OutputMultiset.Add(tp.CreateResult(t)); //Recurse unless we're the last pattern if (pattern < this._triplePatterns.Count - 1) { results = this.StreamingEvaluate(context, pattern + 1, out halt); //If recursion leads to a halt then we halt and return immediately if (halt) { return(results); } //Otherwise we need to keep going here //So must reset our input and outputs before continuing context.InputMultiset = initialInput; context.OutputMultiset = new Multiset(); resultsFound--; } else { //If we're at the last pattern and we've found a match then we can halt halt = true; //Generate the final output and return it if (context.InputMultiset.IsDisjointWith(context.OutputMultiset)) { //Disjoint so do a Product context.OutputMultiset = context.InputMultiset.ProductWithTimeout(context.OutputMultiset, context.QueryTimeout - context.QueryTime); } else { //Normal Join context.OutputMultiset = context.InputMultiset.Join(context.OutputMultiset); } return(context.OutputMultiset); } } } } else if (temp.PatternType == TriplePatternType.Filter) { IFilterPattern fp = (IFilterPattern)temp; ISparqlFilter filter = fp.Filter; ISparqlExpression expr = filter.Expression; //Find the first result of those we've got so far that matches if (context.InputMultiset is IdentityMultiset || context.InputMultiset.IsEmpty) { try { //If the Input is the Identity Multiset then the Output is either //the Identity/Null Multiset depending on whether the Expression evaluates to true if (expr.Evaluate(context, 0).AsSafeBoolean()) { context.OutputMultiset = new IdentityMultiset(); } else { context.OutputMultiset = new NullMultiset(); } } catch { //If Expression fails to evaluate then result is NullMultiset context.OutputMultiset = new NullMultiset(); } } else { foreach (int id in context.InputMultiset.SetIDs) { //Remember to check for Timeout during lazy evaluation context.CheckTimeout(); try { if (expr.Evaluate(context, id).AsSafeBoolean()) { resultsFound++; context.OutputMultiset.Add(context.InputMultiset[id].Copy()); //Recurse unless we're the last pattern if (pattern < this._triplePatterns.Count - 1) { results = this.StreamingEvaluate(context, pattern + 1, out halt); //If recursion leads to a halt then we halt and return immediately if (halt) { return(results); } //Otherwise we need to keep going here //So must reset our input and outputs before continuing context.InputMultiset = initialInput; context.OutputMultiset = new Multiset(); resultsFound--; } else { //If we're at the last pattern and we've found a match then we can halt halt = true; //Generate the final output and return it if (context.InputMultiset.IsDisjointWith(context.OutputMultiset)) { //Disjoint so do a Product context.OutputMultiset = context.InputMultiset.ProductWithTimeout(context.OutputMultiset, context.RemainingTimeout); } else { //Normal Join context.OutputMultiset = context.InputMultiset.Join(context.OutputMultiset); } return(context.OutputMultiset); } } } catch { //Ignore expression evaluation errors } } } } //If we found no possibles we return the null multiset if (resultsFound == 0) { return(new NullMultiset()); } //We should never reach here so throw an error to that effect //The reason we'll never reach here is that this method should always return earlier throw new RdfQueryException("Unexpected control flow in evaluating a Streamed BGP for an ASK query"); }
/// <summary> /// Adds an additional Filter to the Filter Chain /// </summary> /// <param name="filter">A Filter to add</param> protected internal void Add(ISparqlFilter filter) { this._filters.Add(filter); }
/// <summary> /// Creates a new Filter /// </summary> /// <param name="pattern">Algebra the Filter applies over</param> /// <param name="filter">Filter to apply</param> public Filter(ISparqlAlgebra pattern, ISparqlFilter filter) { this._pattern = pattern; this._filter = filter; }
/// <summary> /// Creates a new Chain Filter /// </summary> /// <param name="first">First Filter</param> /// <param name="second">Second Filter</param> public ChainFilter(ISparqlFilter first, ISparqlFilter second) { this._filters.Add(first); this._filters.Add(second); }
/// <summary> /// Creates a new Chain Filter /// </summary> /// <param name="first">First Filter</param> /// <param name="rest">Additional Filters</param> public ChainFilter(ISparqlFilter first, IEnumerable<ISparqlFilter> rest) { this._filters.Add(first); this._filters.AddRange(rest); }
/// <summary> /// Creates a new Filter /// </summary> /// <param name="pattern">Algebra the Filter applies over</param> /// <param name="filter">Filter to apply</param> public Filter(ISparqlAlgebra pattern, ISparqlFilter filter) { _pattern = pattern; _filter = filter; }
/// <summary> /// Creates a new Filter Pattern with the given Filter /// </summary> /// <param name="filter">Filter</param> public FilterPattern(ISparqlFilter filter) { this._filter = filter; this._vars = filter.Variables.Distinct().ToList(); this._vars.Sort(); }
/// <summary> /// Creates a new Variable Restriction Filter /// </summary> /// <param name="pattern">Algebra the filter applies over</param> /// <param name="var">Variable to restrict on</param> /// <param name="filter">Filter to use</param> public VariableRestrictionFilter(ISparqlAlgebra pattern, String var, ISparqlFilter filter) { this._pattern = pattern; this._var = var; this._filter = filter; }
/// <summary> /// Adds a Filter to the Graph Pattern respecting any BGP breaks /// </summary> /// <param name="filter">Filter</param> internal void AddFilter(ISparqlFilter filter) { this._unplacedFilters.Add(filter); }
/// <summary> /// Creates a new Chain Filter /// </summary> /// <param name="first">First Filter</param> /// <param name="second">Second Filter</param> public ChainFilter(ISparqlFilter first, ISparqlFilter second) { _filters.Add(first); _filters.Add(second); }
/// <summary> /// Creates a new LeftJoin where there is a Filter over the join /// </summary> /// <param name="lhs">LHS Pattern</param> /// <param name="rhs">RHS Pattern</param> /// <param name="filter">Filter to decide which RHS solutions are valid</param> public LeftJoin(ISparqlAlgebra lhs, ISparqlAlgebra rhs, ISparqlFilter filter) { this._lhs = lhs; this._rhs = rhs; this._filter = filter; }
/// <summary> /// Adds an additional Filter to the Filter Chain /// </summary> /// <param name="filter">A Filter to add</param> protected internal void Add(ISparqlFilter filter) { _filters.Add(filter); }
/// <summary> /// Creates a new Filter /// </summary> /// <param name="pattern">Algebra the Filter applies over</param> /// <param name="filter">Filter to apply</param> public Filter(ISparqlAlgebra pattern, ISparqlFilter filter) { this._pattern = pattern; this._filter = filter; }
/// <summary> /// Adds a Filter to the Graph Pattern respecting any BGP breaks /// </summary> /// <param name="filter">Filter</param> internal void AddFilter(ISparqlFilter filter) { this._unplacedFilters.Add(filter); }
/// <summary> /// Creates a new Variable Restriction Filter. /// </summary> /// <param name="pattern">Algebra the filter applies over.</param> /// <param name="var">Variable to restrict on.</param> /// <param name="filter">Filter to use.</param> public VariableRestrictionFilter(ISparqlAlgebra pattern, String var, ISparqlFilter filter) { _pattern = pattern; _var = var; _filter = filter; }
/// <summary> /// Creates a new Having Clause /// </summary> /// <param name="pattern">Pattern</param> /// <param name="having">Having Clause</param> public Having(ISparqlAlgebra pattern, ISparqlFilter having) { this._pattern = pattern; this._having = having; }
/// <summary> /// Tries to place filters at the earliest point possible i.e. the first point after which all required variables have occurred /// </summary> /// <param name="gp">Graph Pattern</param> /// <param name="filter">Filter to place</param> /// <returns></returns> private bool TryPlaceFilter(GraphPattern gp, ISparqlFilter filter) { //Firstly we need to find out what variables are needed in the Filter List<String> variablesNeeded = filter.Variables.Distinct().ToList(); //Then we need to move through the Triple Patterns and find the first place at which all the //Variables used in the Filter have been used in ordinary Triple Patterns List<String> variablesUsed = new List<string>(); for (int p = 0; p < gp.TriplePatterns.Count; p++) { if (gp.TriplePatterns[p] is TriplePattern || gp.TriplePatterns[p] is IAssignmentPattern) { foreach (String var in gp.TriplePatterns[p].Variables) { if (!variablesUsed.Contains(var)) variablesUsed.Add(var); } //Have all the Variables we need now been used in a Pattern? if (variablesNeeded.All(v => variablesUsed.Contains(v))) { //We can place this Filter after the Pattern we were just looking at gp.InsertFilter(filter, p + 1); return true; } } } //If we reach here then this means that all the Variables used in the Filter did not occur //in the Triple Patterns which means they likely occur in child graph patterns (or the query //is malformed). In this case we cannot place the Filter and it has to be applied post-commit //rather than during Triple Pattern execution return false; }
/// <summary> /// Inserts a Filter at a given position /// </summary> /// <param name="filter">Filter</param> /// <param name="i">Position to insert at</param> /// <remarks> /// Intended for use by Query Optimisers /// </remarks> public void InsertFilter(ISparqlFilter filter, int i) { if (!this._unplacedFilters.Contains(filter)) throw new RdfQueryException("Cannot Insert a Filter that is not currentlyy an unplaced Filter in this Graph Pattern"); this._unplacedFilters.Remove(filter); FilterPattern p = new FilterPattern(filter); this._triplePatterns.Insert(i, p); }
/// <summary> /// Creates a new Chain Filter /// </summary> /// <param name="first">First Filter</param> /// <param name="rest">Additional Filters</param> public ChainFilter(ISparqlFilter first, IEnumerable <ISparqlFilter> rest) { _filters.Add(first); _filters.AddRange(rest); }
/// <summary> /// Causes the Graph Pattern to be optimised if it isn't already /// </summary> /// <param name="gp">Graph Pattern</param> /// <param name="variables">Variables that have occurred prior to this Pattern</param> public void Optimise(GraphPattern gp, IEnumerable <String> variables) { // Our Variables is initially only those in our Triple Patterns since // anything else is considered to be out of scope List <String> ourVariables = (from tp in gp.TriplePatterns from v in tp.Variables select v).Distinct().ToList(); // Start by sorting the Triple Patterns in the list according to the ranking function gp.TriplePatterns.Sort(GetRankingComparer()); // Apply reordering unless an optimiser has chosen to disable it if (ShouldReorder) { if (gp.TriplePatterns.Count > 0) { // After we sort which gives us a rough optimisation we then may want to reorder // based on the Variables that occurred previous to us OR if we're the Root Graph Pattern if (!variables.Any()) { // Optimise this Graph Pattern // No previously occurring variables so must be the first Graph Pattern if (gp.TriplePatterns.Count > 1) { HashSet <String> currVariables = new HashSet <String>(); gp.TriplePatterns[0].Variables.ForEach(v => currVariables.Add(v)); for (int i = 1; i < gp.TriplePatterns.Count - 1; i++) { if (currVariables.Count == 0) { gp.TriplePatterns[i].Variables.ForEach(v => currVariables.Add(v)); continue; } else if (currVariables.IsDisjoint(gp.TriplePatterns[i].Variables)) { TryReorderPatterns(gp, currVariables.ToList(), i + 1, i); gp.TriplePatterns[i].Variables.ForEach(v => currVariables.Add(v)); } else { gp.TriplePatterns[i].Variables.ForEach(v => currVariables.Add(v)); } } } } else { // Optimise this Graph Pattern based on previously occurring variables if (gp.TriplePatterns.Count > 1 && !gp.TriplePatterns[0].Variables.Any(v => variables.Contains(v)) && variables.Intersect(ourVariables).Any()) { TryReorderPatterns(gp, variables.ToList(), 1, 0); } else if (gp.TriplePatterns.Count > 2) { // In the case where there are more than 2 patterns then we can try and reorder these // in order to further optimise the pattern TryReorderPatterns(gp, gp.TriplePatterns[0].Variables, 2, 1); } } } } if (ShouldPlaceAssignments) { // First we need to place Assignments (LETs) in appropriate places within the Pattern // This happens before Filter placement since Filters may use variables assigned to in LETs if (gp.UnplacedAssignments.Any()) { // Need to ensure that we sort Assignments // This way those that use fewer variables get placed first List <IAssignmentPattern> ps = gp.UnplacedAssignments.OrderBy(x => x).ToList(); // This next bit goes in a do loop as we want to keep attempting to place assignments while // we are able to do so. If the count of unplaced assignments has decreased but is not // zero it may be that we were unable to place some patterns as they relied on variables // assigned in other LETs which weren't placed when we attempted to place them // When we reach the point where no further placements have occurred or all assignments // are placed we stop trying to place assignments int c; do { c = ps.Count; int i = 0; while (i < ps.Count) { if (TryPlaceAssignment(gp, ps[i])) { // Remove from Unplaced Assignments since it's been successfully placed in the Triple Patterns // Don't increment the counter since the next Assignment is now at the index we're already at ps.RemoveAt(i); } else { // Unable to place so increment counter i++; } } } while (c > ps.Count && ps.Count > 0); } } // Regardless of what we've placed already we now place all remaining assignments // foreach (IAssignmentPattern assignment in gp.UnplacedAssignments.ToList()) // { // gp.InsertAssignment(assignment, gp.TriplePatterns.Count); // } if (ShouldPlaceFilters) { // Then we need to place the Filters in appropriate places within the Pattern if (gp.UnplacedFilters.Any()) { if (gp.TriplePatterns.Count == 0) { // Where there are no Triple Patterns the Graph Pattern just contains this Filter and possibly some // child Graph Patterns. In such a case then we shouldn't place the Filters } else { if (ShouldSplitFilters) { // See whether we can split any/all of the Unplaced Filters List <ISparqlFilter> fs = gp.UnplacedFilters.ToList(); for (int i = 0; i < fs.Count; i++) { ISparqlFilter f = fs[i]; if (f.Expression is AndExpression) { // Split the And // Note that multiple nested And's are handled by the fact that we will continue working through the list until it is finished UnaryExpressionFilter lhs = new UnaryExpressionFilter(f.Expression.Arguments.First()); UnaryExpressionFilter rhs = new UnaryExpressionFilter(f.Expression.Arguments.Last()); fs.RemoveAt(i); fs.Add(lhs); fs.Add(rhs); } } // Finally we need to ensure the Unplaced Filters list is appropriately updated gp.ResetFilters(fs); } foreach (ISparqlFilter f in gp.UnplacedFilters.ToList()) { TryPlaceFilter(gp, f); } } } } // Finally optimise the Child Graph Patterns foreach (GraphPattern cgp in gp.ChildGraphPatterns) { // At each point the variables that have occurred are those in the Triple Patterns and // those in previous Graph Patterns cgp.Optimise(this, ourVariables); ourVariables.AddRange(cgp.Variables); } // Note: Any remaining Unplaced Filters/Assignments are OK since the ToAlgebra() method of a GraphPattern // will take care of placing these appropriately }
/// <summary> /// Creates a new Having Clause /// </summary> /// <param name="pattern">Pattern</param> /// <param name="having">Having Clause</param> public Having(ISparqlAlgebra pattern, ISparqlFilter having) { this._pattern = pattern; this._having = having; }
/// <summary> /// Creates a new LeftJoin where there is a Filter over the join /// </summary> /// <param name="lhs">LHS Pattern</param> /// <param name="rhs">RHS Pattern</param> /// <param name="filter">Filter to decide which RHS solutions are valid</param> public LeftJoin(ISparqlAlgebra lhs, ISparqlAlgebra rhs, ISparqlFilter filter) : this(lhs, rhs) { _filter = filter; }