                 } else {
                     return Term(string: "")
                 }
-            } else if stringFunction == .strafter {
+            case .strafter:
                 guard pattern.value != "" else { return string }
                 if let range = string.value.range(of: pattern.value) {
                     let suffix = String(string.value[range.upperBound...])

                }
                return Term(string: encoded)
            default:
                fatalError()
            }
        case .contains, .strstarts, .strends, .strbefore, .strafter:
            try guardArity(terms.count, 2, stringFunction.rawValue)
            guard let string = terms[0], let pattern = terms[1] else { throw QueryError.evaluationError("Not all arguments are bound in \(stringFunction) call") }
            try throwUnlessArgumentCompatible(string, pattern)
            if stringFunction == .contains {
                return Term(boolean: string.value.contains(pattern.value))
            } else if stringFunction == .strstarts {
                return Term(boolean: string.value.hasPrefix(pattern.value))
            } else if stringFunction == .strends {
                return Term(boolean: string.value.hasSuffix(pattern.value))
            } else if stringFunction == .strbefore {
                guard pattern.value != "" else { return Term(value: "", type: string.type) }
                if let range = string.value.range(of: pattern.value) {
                    let prefix = String(string.value[..<range.lowerBound])

    public func numericEvaluate(expression: Expression, result: TermResult) throws -> NumericValue {
        switch expression {
        case .aggregate(_):
            fatalError("Cannot evaluate an aggregate expression without a query context")
        case .node(.bound(let term)):
            if let num = term.numeric {
                return num
