├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── SPARQLSyntax │ ├── Aggregation.swift │ ├── Algebra.swift │ ├── Expression.swift │ ├── IRI.swift │ ├── PropertyPath.swift │ ├── Query.swift │ ├── RDF.swift │ ├── RDFPatterns.swift │ ├── RDFSerialization.swift │ ├── SPARQLLexer.swift │ ├── SPARQLParser.swift │ ├── SPARQLSerializer.swift │ ├── SPARQLSyntax.swift │ ├── Window.swift │ └── XSD.swift ├── sparql-parser │ └── main.swift ├── sparql-test │ └── main.swift └── sparqllint │ └── main.swift ├── Tests ├── LinuxMain.swift └── SPARQLSyntaxTests │ ├── AlgebraTests.swift │ ├── ExpressionTests.swift │ ├── IRITests.swift │ ├── PropertyPathTests.swift │ ├── RDFPatternsTests.swift │ ├── RDFTests.swift │ ├── SPARQLParserTests.swift │ ├── SPARQLParserWindowTests.swift │ ├── SPARQLReformattingTests.swift │ ├── SPARQLRewriting.swift │ ├── SPARQLSerializationTests.swift │ ├── XCTestManifests.swift │ └── XSDTests.swift ├── docs └── Extensions.md └── examples └── messy.rq /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018–2021 Gregory Todd Williams 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.7 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SPARQLSyntax", 8 | platforms: [ 9 | .macOS(.v13) 10 | ], 11 | products: [ 12 | .library( 13 | name: "SPARQLSyntax", 14 | targets: ["SPARQLSyntax"]), 15 | .executable( 16 | name: "sparqllint", 17 | targets: ["sparqllint"]), 18 | .executable( 19 | name: "sparql-parser", 20 | targets: ["sparql-parser"]), 21 | ], 22 | dependencies: [ 23 | .package(url: "https://github.com/kasei/swift-serd.git", .upToNextMinor(from: "0.0.4")) 24 | ], 25 | targets: [ 26 | .executableTarget( 27 | name: "sparqllint", 28 | dependencies: ["SPARQLSyntax"] 29 | ), 30 | .executableTarget( 31 | name: "sparql-parser", 32 | dependencies: ["SPARQLSyntax"] 33 | ), 34 | .target( 35 | name: "SPARQLSyntax", 36 | dependencies: [ 37 | .product(name: "serd", package: "swift-serd") 38 | ] 39 | ), 40 | .testTarget( 41 | name: "SPARQLSyntaxTests", 42 | dependencies: ["SPARQLSyntax"] 43 | ), 44 | ] 45 | ) 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SPARQLSyntax 2 | 3 | ## SPARQL 1.1 Parser and Abstract Syntax 4 | 5 | - [Features](#features) 6 | - [Building](#building) 7 | - [Swift Package Manager](#swift-package-manager) 8 | - [Command Line Usage](#command-line-usage) 9 | - [API](#api) 10 | - [Term](#term) 11 | - [Triple, Quad, TriplePattern and QuadPattern](#triple-quad-triplepattern-and-quadpattern) 12 | - [Algebra](#algebra) 13 | - [Expression](#expression) 14 | - [Query](#query) 15 | - [SPARQLParser](#sparqlparser) 16 | - [SPARQLSerializer](#sparqlserializer) 17 | - [Extensions](#extensions) 18 | - [Window Functions](#window-functions) 19 | 20 | ### Features 21 | 22 | * [SPARQL 1.1] Parser, Tokenizer, and Serializer available via both API and command line tool 23 | * Abstract syntax representation of SPARQL queries, aligned with the [SPARQL Algebra] 24 | * Supported extensions: 25 | - [Window Functions](#window-functions) 26 | 27 | ### Building 28 | 29 | ``` 30 | % swift build -c release 31 | ``` 32 | 33 | ### Swift Package Manager 34 | 35 | To use SPARQLSyntax with projects using the [Swift Package Manager], 36 | add the following to your project's `Package.swift` file: 37 | 38 | ```swift 39 | dependencies: [ 40 | .package(url: "https://github.com/kasei/swift-sparql-syntax.git", .upToNextMinor(from: "0.0.91")) 41 | ] 42 | ``` 43 | 44 | ### Command Line Usage 45 | 46 | A command line tool, `sparql-parser`, is provided to parse a SPARQL query and 47 | print its parsed query algebra, its tokenization, or a pretty-printed SPARQL 48 | string: 49 | 50 | ``` 51 | % ./.build/release/sparql-parser 52 | Usage: ./.build/release/sparql-parser [-v] COMMAND [ARGUMENTS] 53 | ./.build/release/sparql-parser parse query.rq 54 | ./.build/release/sparql-parser lint query.rq 55 | ./.build/release/sparql-parser tokens query.rq 56 | ``` 57 | 58 | To "lint", or "pretty print", a SPARQL query: 59 | 60 | ``` 61 | % cat examples/messy.rq 62 | prefix geo: 63 | select ?s 64 | where{ 65 | ?s geo:lat ?lat ;geo:long ?long ; 66 | FILTER(?long < -117.0) 67 | FILTER(?lat >= 31.0) 68 | FILTER(?lat <= 33.0) 69 | } ORDER BY ?s 70 | 71 | % ./.build/release/sparql-parser lint examples/messy.rq 72 | PREFIX geo: 73 | SELECT ?s WHERE { 74 | ?s geo:lat ?lat ; 75 | geo:long ?long ; 76 | FILTER (?long < - 117.0) 77 | FILTER (?lat >= 31.0) 78 | FILTER (?lat <= 33.0) 79 | } 80 | ORDER BY ?s 81 | 82 | ``` 83 | 84 | To parse the query and print the resulting query algebra: 85 | 86 | ``` 87 | % ./.build/release/sparql-parser parse examples/messy.rq 88 | Query 89 | Select { ?s } 90 | Project { ?s } 91 | OrderBy { ?s } 92 | Filter (((?long < -117.0) && (?lat >= 31.0)) && (?lat <= 33.0)) 93 | BGP 94 | ?s ?lat . 95 | ?s ?long . 96 | 97 | ``` 98 | 99 | ### API 100 | 101 | The `SPARQLSyntax` library provides an API for parsing SPARQL queries 102 | and accessing the resulting abstract data structures. 103 | The primary components of this API are: 104 | 105 | * `struct Term` - A representation of an RDF Term (IRI, Literal, or Blank node) 106 | * `enum Algebra` - A representation of the query pattern closely aligned with the formal SPARQL Algebra 107 | * `enum Expression` - A representation of a logical expression 108 | * `struct Query` - A representation of a SPARQL query including: a query form (`SELECT`, `ASK`, `DESCRIBE`, or `CONSTRUCT`), a query `Algebra`, and optional base URI and dataset specification 109 | * `struct SPARQLParser` - Parses a SPARQL query String/Data and returns a `Query` 110 | * `struct SPARQLSerializer` - Provides the ability to serialize a query, optionally applying "pretty printing" formatting 111 | 112 | #### `Term` 113 | 114 | `struct Term` represents an [RDF Term] (an IRI, a blank node, or an RDF Literal). 115 | `Term` also provides some support for XSD numeric types, 116 | bridging between `Term`s and `enum NumericValue` which provides numeric functions and [type-promoting operators](https://www.w3.org/TR/xpath20/#promotion). 117 | 118 | #### `Triple`, `Quad`, `TriplePattern`, and `QuadPattern` 119 | 120 | `struct Triple` and `struct Quad` combine `Term`s into RDF triples and quads. 121 | `struct TriplePattern` and `struct QuadPattern` represent patterns which can be matched by concrete `Triple`s and `Quad`s. 122 | Instead of `Term`s, patterns are comprised of a `enum Node` which can be either a bound `Term`, or a named `variable`. 123 | 124 | #### `Algebra` 125 | 126 | `enum Algebra` is an representation of a query pattern aligned with the [SPARQL Algebra]. 127 | Cases include simple graph pattern matching such as `triple`, `quad`, and `bgp`, 128 | and more complex operators that can be used to join other `Algebra` values 129 | (e.g. `innerJoin`, `union`, `project`, `distinct`). 130 | 131 | `Algebra` provides functions and properties to access features of graph patterns including: 132 | variables used; and in-scope, projectable, and "necessarily bound" variables. 133 | The structure of `Algebra` values can be modified using a rewriting API that can: 134 | bind values to specific variables; replace entire `Algebra` sub-trees; and rewrite `Expression`s used within the `Algebra`. 135 | 136 | #### `Expression` 137 | 138 | `enum Expression` represents a logical expression of variables, values, operators, and functions 139 | that can be evaluated within the context of a query result to produce a `Term` value. 140 | `Expression`s are used in the following `Algebra` operations: filter, left outer join ("OPTIONAL"), extend ("BIND"), and aggregate. 141 | 142 | `Expression`s may be modified using a similar rewriting API to that provided by `Algebra` that can: 143 | bind values to specific variables; and replace entire `Expression` sub-trees. 144 | 145 | #### `Query` 146 | 147 | `struct Query` represents a SPARQL Query and includes: 148 | 149 | * a query form (`SELECT`, `ASK`, `DESCRIBE`, or `CONSTRUCT`, and any associated data such as projected variables, or triple patterns used to `CONSTRUCT` a result graph) 150 | * a graph pattern (`Algebra`) 151 | * an optional base URI 152 | * an optional dataset specification 153 | 154 | #### `SPARQLParser` 155 | 156 | `struct SPARQLParser` provides an API for parsing a SPARQL 1.1 query string and producing a `Query`. 157 | 158 | #### `SPARQLSerializer` 159 | 160 | `struct SPARQLSerializer` provides an API for serializing SPARQL 1.1 queries, optionally applying "pretty printing" rules to produce consistently formatted output. 161 | It can serialize both structured queries (`Query` and `Algebra`) and unstructured queries (a query `String`). 162 | In the latter case, serialization can be used even if the query contains syntax errors (with data after the error being serialized as-is). 163 | 164 | ### Extensions 165 | 166 | #### Window Functions 167 | 168 | Parsing of window functions is supported as an extension to the SPARQL 1.1 syntax. 169 | A SQL-like syntax is supported for projecting window functions in a `SELECT` clause, as well as in a `HAVING` clause. 170 | In addition to the built-in aggregate functions, the following window functions are supported: 171 | `RANK`, `ROW_NUMBER`. 172 | 173 | Shown below are some examples of the supported syntax. 174 | 175 | ```swift 176 | # "Limit By Resource" 177 | # This query limits results to two name/school pairs per person 178 | PREFIX foaf: 179 | SELECT ?name ?school WHERE { 180 | ?s a foaf:Person ; 181 | foaf:name ?name ; 182 | foaf:schoolHomepage ?school 183 | } 184 | HAVING (RANK() OVER (PARTITION BY ?s) < 2) 185 | ``` 186 | 187 | ```swift 188 | # Use window framing to compute a moving average over the trailing four results 189 | PREFIX : 190 | SELECT (AVG(?value) OVER (ORDER BY ?date ROWS BETWEEN 3 PRECEDING AND CURRENT ROW) AS ?movingAverage) WHERE { 191 | VALUES (?date ?value) { 192 | (1 1.0) 193 | (2 2.0) 194 | (3 3.0) 195 | (4 2.0) 196 | (5 0.0) 197 | (6 0.0) 198 | (7 1.0) 199 | } 200 | } 201 | ``` 202 | 203 | 204 | [SPARQL 1.1]: https://www.w3.org/TR/sparql11-query 205 | [SPARQL Algebra]: https://www.w3.org/TR/sparql11-query/#sparqlAlgebra 206 | [Swift Package Manager]: https://swift.org/package-manager 207 | [RDF Term]: https://www.w3.org/TR/sparql11-query/#sparqlBasicTerms 208 | -------------------------------------------------------------------------------- /Sources/SPARQLSyntax/Aggregation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Aggregation.swift 3 | // SPARQLSyntax 4 | // 5 | // Created by Gregory Todd Williams on 6/3/18. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum Aggregation : Equatable, Hashable { 11 | case countAll(Bool) 12 | case count(Expression, Bool) 13 | case sum(Expression, Bool) 14 | case avg(Expression, Bool) 15 | case min(Expression) 16 | case max(Expression) 17 | case sample(Expression) 18 | case groupConcat(Expression, String, Bool) 19 | 20 | public var variables: Set { 21 | switch self { 22 | case .countAll: 23 | return Set() 24 | case .count(let e, _), 25 | .sum(let e, _), 26 | .avg(let e, _), 27 | .min(let e), 28 | .max(let e), 29 | .sample(let e), 30 | .groupConcat(let e, _, _): 31 | return e.variables 32 | } 33 | } 34 | 35 | var expression: Expression? { 36 | switch self { 37 | case .countAll: 38 | return nil 39 | case .count(let e, _), 40 | .sum(let e, _), 41 | .avg(let e, _), 42 | .min(let e), 43 | .max(let e), 44 | .sample(let e), 45 | .groupConcat(let e, _, _): 46 | return e 47 | } 48 | } 49 | } 50 | 51 | extension Aggregation: Codable { 52 | private enum CodingKeys: String, CodingKey { 53 | case type 54 | case expression 55 | case distinct 56 | case string 57 | } 58 | 59 | public init(from decoder: Decoder) throws { 60 | let container = try decoder.container(keyedBy: CodingKeys.self) 61 | let type = try container.decode(String.self, forKey: .type) 62 | switch type { 63 | case "countAll": 64 | let distinct = try container.decode(Bool.self, forKey: .distinct) 65 | self = .countAll(distinct) 66 | case "count": 67 | let expr = try container.decode(Expression.self, forKey: .expression) 68 | let distinct = try container.decode(Bool.self, forKey: .distinct) 69 | self = .count(expr, distinct) 70 | case "sum": 71 | let expr = try container.decode(Expression.self, forKey: .expression) 72 | let distinct = try container.decode(Bool.self, forKey: .distinct) 73 | self = .sum(expr, distinct) 74 | case "avg": 75 | let expr = try container.decode(Expression.self, forKey: .expression) 76 | let distinct = try container.decode(Bool.self, forKey: .distinct) 77 | self = .avg(expr, distinct) 78 | case "min": 79 | let expr = try container.decode(Expression.self, forKey: .expression) 80 | self = .min(expr) 81 | case "max": 82 | let expr = try container.decode(Expression.self, forKey: .expression) 83 | self = .max(expr) 84 | case "sample": 85 | let expr = try container.decode(Expression.self, forKey: .expression) 86 | self = .sample(expr) 87 | case "groupConcat": 88 | let expr = try container.decode(Expression.self, forKey: .expression) 89 | let distinct = try container.decode(Bool.self, forKey: .distinct) 90 | let sep = try container.decode(String.self, forKey: .string) 91 | self = .groupConcat(expr, sep, distinct) 92 | default: 93 | throw SPARQLSyntaxError.serializationError("Unexpected aggregation type '\(type)' found") 94 | } 95 | } 96 | 97 | public func encode(to encoder: Encoder) throws { 98 | var container = encoder.container(keyedBy: CodingKeys.self) 99 | switch self { 100 | case .countAll(let distinct): 101 | try container.encode("countAll", forKey: .type) 102 | try container.encode(distinct, forKey: .distinct) 103 | case let .count(expr, distinct): 104 | try container.encode("count", forKey: .type) 105 | try container.encode(expr, forKey: .expression) 106 | try container.encode(distinct, forKey: .distinct) 107 | case let .sum(expr, distinct): 108 | try container.encode("sum", forKey: .type) 109 | try container.encode(expr, forKey: .expression) 110 | try container.encode(distinct, forKey: .distinct) 111 | case let .avg(expr, distinct): 112 | try container.encode("avg", forKey: .type) 113 | try container.encode(expr, forKey: .expression) 114 | try container.encode(distinct, forKey: .distinct) 115 | case let .min(expr): 116 | try container.encode("min", forKey: .type) 117 | try container.encode(expr, forKey: .expression) 118 | case let .max(expr): 119 | try container.encode("max", forKey: .type) 120 | try container.encode(expr, forKey: .expression) 121 | case let .sample(expr): 122 | try container.encode("sample", forKey: .type) 123 | try container.encode(expr, forKey: .expression) 124 | case let .groupConcat(expr, sep, distinct): 125 | try container.encode("groupConcat", forKey: .type) 126 | try container.encode(expr, forKey: .expression) 127 | try container.encode(distinct, forKey: .distinct) 128 | try container.encode(sep, forKey: .string) 129 | } 130 | } 131 | } 132 | 133 | extension Aggregation: CustomStringConvertible { 134 | public var description: String { 135 | switch self { 136 | case .countAll(true): 137 | return "COUNT(DISTINCT *)" 138 | case .countAll(false): 139 | return "COUNT(*)" 140 | case .count(let expr, false): 141 | return "COUNT(\(expr.description))" 142 | case .count(let expr, true): 143 | return "COUNT(DISTINCT \(expr.description))" 144 | case .sum(let expr, false): 145 | return "SUM(\(expr.description))" 146 | case .sum(let expr, true): 147 | return "SUM(DISTINCT \(expr.description))" 148 | case .avg(let expr, false): 149 | return "AVG(\(expr.description))" 150 | case .avg(let expr, true): 151 | return "AVG(DISTINCT \(expr.description))" 152 | case .min(let expr): 153 | return "MIN(\(expr.description))" 154 | case .max(let expr): 155 | return "MAX(\(expr.description))" 156 | case .sample(let expr): 157 | return "SAMPLE(\(expr.description))" 158 | case .groupConcat(let expr, let sep, let distinct): 159 | let e = distinct ? "DISTINCT \(expr.description)" : expr.description 160 | if sep == " " { 161 | return "GROUP_CONCAT(\(e))" 162 | } else { 163 | return "GROUP_CONCAT(\(e); SEPARATOR=\"\(sep)\")" 164 | } 165 | } 166 | } 167 | } 168 | 169 | public extension Aggregation { 170 | func replace(_ map: [String:Term]) throws -> Aggregation { 171 | let nodes = map.mapValues { Node.bound($0) } 172 | return try self.replace(nodes) 173 | } 174 | 175 | func replace(_ map: [String:Node]) throws -> Aggregation { 176 | switch self { 177 | case .countAll: 178 | return self 179 | case .count(let expr, let distinct): 180 | return try .count(expr.replace(map), distinct) 181 | case .sum(let expr, let distinct): 182 | return try .sum(expr.replace(map), distinct) 183 | case .avg(let expr, let distinct): 184 | return try .avg(expr.replace(map), distinct) 185 | case .min(let expr): 186 | return try .min(expr.replace(map)) 187 | case .max(let expr): 188 | return try .max(expr.replace(map)) 189 | case .sample(let expr): 190 | return try .sample(expr.replace(map)) 191 | case .groupConcat(let expr, let sep, let distinct): 192 | return try .groupConcat(expr.replace(map), sep, distinct) 193 | } 194 | } 195 | 196 | func replace(_ map: (Expression) throws -> Expression?) throws -> Aggregation { 197 | switch self { 198 | case .countAll: 199 | return self 200 | case .count(let expr, let distinct): 201 | return try .count(expr.replace(map), distinct) 202 | case .sum(let expr, let distinct): 203 | return try .sum(expr.replace(map), distinct) 204 | case .avg(let expr, let distinct): 205 | return try .avg(expr.replace(map), distinct) 206 | case .min(let expr): 207 | return try .min(expr.replace(map)) 208 | case .max(let expr): 209 | return try .max(expr.replace(map)) 210 | case .sample(let expr): 211 | return try .sample(expr.replace(map)) 212 | case .groupConcat(let expr, let sep, let distinct): 213 | return try .groupConcat(expr.replace(map), sep, distinct) 214 | } 215 | } 216 | 217 | func rewrite(_ map: (Expression) throws -> RewriteStatus) throws -> Aggregation { 218 | switch self { 219 | case .countAll: 220 | return self 221 | case .count(let expr, let distinct): 222 | return try .count(expr.rewrite(map), distinct) 223 | case .sum(let expr, let distinct): 224 | return try .sum(expr.rewrite(map), distinct) 225 | case .avg(let expr, let distinct): 226 | return try .avg(expr.rewrite(map), distinct) 227 | case .min(let expr): 228 | return try .min(expr.rewrite(map)) 229 | case .max(let expr): 230 | return try .max(expr.rewrite(map)) 231 | case .sample(let expr): 232 | return try .sample(expr.rewrite(map)) 233 | case .groupConcat(let expr, let sep, let distinct): 234 | return try .groupConcat(expr.rewrite(map), sep, distinct) 235 | } 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /Sources/SPARQLSyntax/IRI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IRI.swift 3 | // SPARQLSyntax 4 | // 5 | // Created by Gregory Todd Williams on 5/22/18. 6 | // 7 | 8 | import Foundation 9 | import serd 10 | 11 | @dynamicMemberLookup 12 | public struct Namespace { 13 | public static var xsd = Namespace(value: "http://www.w3.org/2001/XMLSchema#") 14 | public static var rdf = Namespace(value: "http://www.w3.org/1999/02/22-rdf-syntax-ns#") 15 | public static var rdfs = Namespace(value: "http://www.w3.org/2000/01/rdf-schema#") 16 | public static var sd = Namespace(value: "http://www.w3.org/ns/sparql-service-description#") 17 | public static var hydra = Namespace(value: "http://www.w3.org/ns/hydra/core#") 18 | public static var void = Namespace(value: "http://rdfs.org/ns/void#") 19 | public static var formats = Namespace(value: "http://www.w3.org/ns/formats/") 20 | 21 | var value: String 22 | public subscript(dynamicMember member: String) -> String { 23 | return value.appending(member) 24 | } 25 | 26 | public func iriString(for local: String) -> String { 27 | let v = value.appending(local) 28 | return v 29 | } 30 | 31 | public func iri(for local: String) -> IRI? { 32 | return IRI(string: iriString(for: local)) 33 | } 34 | 35 | public init(value: String) { 36 | self.value = value 37 | } 38 | 39 | public init(url: URL) { 40 | self.value = url.absoluteString 41 | } 42 | } 43 | 44 | public class IRI : Codable { 45 | public let absoluteString: String 46 | 47 | convenience public init?(string: String) { 48 | self.init(string: string, relativeTo: nil) 49 | } 50 | 51 | public init?(fileURLWithPath path: String) { 52 | do { 53 | var uri = SERD_URI_NULL 54 | absoluteString = try withUnsafeMutablePointer(to: &uri) { (u) throws -> String in 55 | let uu = UnsafeMutablePointer(u) 56 | var data = path.data(using: .utf8)! 57 | var node = try data.withUnsafeMutableBytes { (bp : UnsafeMutableRawBufferPointer) -> SerdNode in 58 | guard let p = bp.baseAddress?.assumingMemoryBound(to: UInt8.self) else { 59 | throw IRIError.encodingError("Failed to bind IRI memory to expected type") 60 | } 61 | return serd_node_new_file_uri(p, nil, uu, false) 62 | } 63 | defer { 64 | withUnsafeMutablePointer(to: &node) { (n) in 65 | serd_node_free(n) 66 | } 67 | } 68 | let absolute = try u.pointee.value() 69 | return absolute 70 | } 71 | } catch { 72 | print("IRI error: \(error)") 73 | return nil 74 | } 75 | } 76 | 77 | public init?(string: String, relativeTo iri: IRI?) { 78 | if let iri = iri { 79 | let baseString = iri.absoluteString 80 | var rel = SERD_URI_NULL 81 | // print("<\(iri.absoluteString ?? "")> + <\(string)>") 82 | let absolute = withUnsafeMutablePointer(to: &rel) { (r) -> String? in 83 | guard var stringData = string.cString(using: .utf8) else { return nil } 84 | return stringData.withUnsafeMutableBytes { (bytes) -> String? in 85 | guard let stringPtr = bytes.baseAddress?.assumingMemoryBound(to: UInt8.self) else { return nil } 86 | let status = serd_uri_parse(stringPtr, r) 87 | guard status == SERD_SUCCESS else { return nil } 88 | // try? print("Relative IRI: <\(r.pointee.value())>") 89 | var base = SERD_URI_NULL 90 | return withUnsafeMutablePointer(to: &base) { (b) -> String? in 91 | guard var baseData = baseString.cString(using: .utf8) else { return nil } 92 | return baseData.withUnsafeMutableBytes { (baseBytes) -> String? in 93 | guard let basePtr = baseBytes.baseAddress?.assumingMemoryBound(to: UInt8.self) else { return nil } 94 | let status = serd_uri_parse(basePtr, b) 95 | guard status == SERD_SUCCESS else { return nil } 96 | // try? print("Base IRI: <\(b.pointee.value())>") 97 | var uri = SERD_URI_NULL 98 | return withUnsafeMutablePointer(to: &uri) { (out) -> String? in 99 | let rr = UnsafePointer(r) 100 | let bb = UnsafePointer(b) 101 | serd_uri_resolve(rr, bb, out) 102 | let absolute = try? out.pointee.value() 103 | return absolute 104 | } 105 | } 106 | } 107 | } 108 | } 109 | 110 | if let a = absolute { 111 | absoluteString = a 112 | } else { 113 | return nil 114 | } 115 | } else { 116 | do { 117 | var uri = SERD_URI_NULL 118 | let absolute = try withUnsafeMutablePointer(to: &uri) { (u) throws -> String? in 119 | guard var stringData = string.cString(using: .utf8) else { return nil } 120 | return try stringData.withUnsafeMutableBytes { (bytes) throws -> String? in 121 | guard let stringPtr = bytes.baseAddress?.assumingMemoryBound(to: UInt8.self) else { return nil } 122 | let status = serd_uri_parse(stringPtr, u) 123 | if status == SERD_SUCCESS { 124 | return try u.pointee.value() 125 | } else { 126 | return nil 127 | } 128 | } 129 | } 130 | if let a = absolute { 131 | absoluteString = a 132 | } else { 133 | return nil 134 | } 135 | } catch { 136 | print("IRI error: \(error)") 137 | return nil 138 | } 139 | } 140 | } 141 | 142 | public var url : URL? { 143 | return URL(string: absoluteString) 144 | } 145 | } 146 | 147 | @dynamicMemberLookup 148 | public struct TermNamespace { 149 | public var namespace: Namespace 150 | public init(namespace: Namespace) { 151 | self.namespace = namespace 152 | } 153 | 154 | public subscript(dynamicMember member: String) -> Term { 155 | let i = namespace.iriString(for: member) 156 | return Term(iri: i) 157 | } 158 | } 159 | 160 | @dynamicMemberLookup 161 | public struct NodeNamespace { 162 | public var namespace: Namespace 163 | public init(namespace: Namespace) { 164 | self.namespace = namespace 165 | } 166 | 167 | public subscript(dynamicMember member: String) -> Node { 168 | let i = namespace.iriString(for: member) 169 | return .bound(Term(iri: i)) 170 | } 171 | } 172 | 173 | public enum IRIError : Error { 174 | case encodingError(String) 175 | } 176 | 177 | fileprivate extension SerdChunk { 178 | var defined : Bool { 179 | if let _ = self.buf { 180 | return true 181 | } else { 182 | return false 183 | } 184 | } 185 | 186 | func value() throws -> String { 187 | if let buf = self.buf { 188 | var data = Data() 189 | data.append(buf, count: self.len) 190 | guard let string = String(data: data, encoding: .utf8) else { 191 | throw IRIError.encodingError("Failed to turn \(self.len) bytes from SerdChunk into string: <\(data)>") 192 | } 193 | return string 194 | } else { 195 | return "" 196 | } 197 | } 198 | } 199 | 200 | fileprivate extension SerdURI { 201 | func value() throws -> String { 202 | var value = "" 203 | value += try self.scheme.value() 204 | value += ":" 205 | if self.authority.defined { 206 | value += "//" 207 | } 208 | value += try self.authority.value() 209 | value += try self.path_base.value() 210 | value += try self.path.value() 211 | let query = try self.query.value() 212 | if query.count > 0 { 213 | value += "?" 214 | value += try self.query.value() 215 | } 216 | value += try self.fragment.value() 217 | return value 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /Sources/SPARQLSyntax/PropertyPath.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PropertyPath.swift 3 | // SPARQLSyntax 4 | // 5 | // Created by Gregory Todd Williams on 8/28/18. 6 | // 7 | 8 | import Foundation 9 | 10 | public indirect enum PropertyPath: Hashable, Equatable { 11 | case link(Term) 12 | case inv(PropertyPath) 13 | case nps([Term]) 14 | case alt(PropertyPath, PropertyPath) 15 | case seq(PropertyPath, PropertyPath) 16 | case plus(PropertyPath) 17 | case star(PropertyPath) 18 | case zeroOrOne(PropertyPath) 19 | } 20 | 21 | extension PropertyPath : Codable { 22 | private enum CodingKeys: String, CodingKey { 23 | case type 24 | case link 25 | case lhs 26 | case rhs 27 | case terms 28 | } 29 | 30 | public init(from decoder: Decoder) throws { 31 | let container = try decoder.container(keyedBy: CodingKeys.self) 32 | let type = try container.decode(String.self, forKey: .type) 33 | switch type { 34 | case "link": 35 | let term = try container.decode(Term.self, forKey: .link) 36 | self = .link(term) 37 | case "inv": 38 | let pp = try container.decode(PropertyPath.self, forKey: .lhs) 39 | self = .inv(pp) 40 | case "nps": 41 | let terms = try container.decode([Term].self, forKey: .terms) 42 | self = .nps(terms) 43 | case "alt": 44 | let lhs = try container.decode(PropertyPath.self, forKey: .lhs) 45 | let rhs = try container.decode(PropertyPath.self, forKey: .rhs) 46 | self = .alt(lhs, rhs) 47 | case "seq": 48 | let lhs = try container.decode(PropertyPath.self, forKey: .lhs) 49 | let rhs = try container.decode(PropertyPath.self, forKey: .rhs) 50 | self = .seq(lhs, rhs) 51 | case "plus": 52 | let pp = try container.decode(PropertyPath.self, forKey: .lhs) 53 | self = .plus(pp) 54 | case "star": 55 | let pp = try container.decode(PropertyPath.self, forKey: .lhs) 56 | self = .star(pp) 57 | case "zeroOrOne": 58 | let pp = try container.decode(PropertyPath.self, forKey: .lhs) 59 | self = .zeroOrOne(pp) 60 | default: 61 | throw SPARQLSyntaxError.serializationError("Unexpected property path type '\(type)' found") 62 | } 63 | } 64 | 65 | public func encode(to encoder: Encoder) throws { 66 | var container = encoder.container(keyedBy: CodingKeys.self) 67 | switch self { 68 | case .link(let term): 69 | try container.encode("link", forKey: .type) 70 | try container.encode(term, forKey: .link) 71 | case .inv(let pp): 72 | try container.encode("inv", forKey: .type) 73 | try container.encode(pp, forKey: .lhs) 74 | case .nps(let terms): 75 | try container.encode("nps", forKey: .type) 76 | try container.encode(terms, forKey: .terms) 77 | case let .alt(lhs, rhs): 78 | try container.encode("alt", forKey: .type) 79 | try container.encode(lhs, forKey: .lhs) 80 | try container.encode(rhs, forKey: .rhs) 81 | case let .seq(lhs, rhs): 82 | try container.encode("seq", forKey: .type) 83 | try container.encode(lhs, forKey: .lhs) 84 | try container.encode(rhs, forKey: .rhs) 85 | case .plus(let pp): 86 | try container.encode("plus", forKey: .type) 87 | try container.encode(pp, forKey: .lhs) 88 | case .star(let pp): 89 | try container.encode("star", forKey: .type) 90 | try container.encode(pp, forKey: .lhs) 91 | case .zeroOrOne(let pp): 92 | try container.encode("zeroOrOne", forKey: .type) 93 | try container.encode(pp, forKey: .lhs) 94 | } 95 | } 96 | } 97 | 98 | extension PropertyPath: CustomStringConvertible { 99 | public var description: String { 100 | switch self { 101 | case .link(let t): 102 | return t.description 103 | case .inv(let pp): 104 | return "inv(\(pp))" 105 | case .nps(let pp): 106 | return "NPS(\(pp))" 107 | case let .alt(lhs, rhs): 108 | return "alt(\(lhs), \(rhs))" 109 | case let .seq(lhs, rhs): 110 | return "seq(\(lhs), \(rhs))" 111 | case .plus(let pp): 112 | return "oneOrMore(\(pp))" 113 | case .star(let pp): 114 | return "zeroOrMore(\(pp))" 115 | case .zeroOrOne(let pp): 116 | return "zeroOrOne(\(pp))" 117 | } 118 | } 119 | } 120 | 121 | extension PropertyPath : Comparable { 122 | public static func < (lhs: PropertyPath, rhs: PropertyPath) -> Bool { 123 | switch (lhs, rhs) { 124 | case let (.link(l), .link(r)): 125 | return l < r 126 | case let (.inv(l), .inv(r)), let (.plus(l), .plus(r)), let (.star(l), .star(r)), let (.zeroOrOne(l), .zeroOrOne(r)): 127 | return l < r 128 | case let (.seq(ll, lr), .seq(rl, rr)), let (.alt(ll, lr), .alt(rl, rr)): 129 | if ll < rl { return true } 130 | if ll == rl && lr < rr { return true } 131 | return false 132 | case let (.nps(l), .nps(r)): 133 | for (li, ri) in zip(l, r) { 134 | if li < ri { return true } 135 | if li > ri { return false } 136 | } 137 | return false 138 | case (.link, _): 139 | return true 140 | case (.inv, .nps), (.inv, .alt), (.inv, .seq), (.inv, .plus), (.inv, .star), (.inv, .zeroOrOne): 141 | return true 142 | case (.nps, .alt), (.nps, .seq), (.nps, .plus), (.nps, .star), (.nps, .zeroOrOne): 143 | return true 144 | case (.alt, .seq), (.alt, .plus), (.alt, .star), (.alt, .zeroOrOne): 145 | return true 146 | case (.seq, .plus), (.seq, .star), (.seq, .zeroOrOne): 147 | return true 148 | case (.plus, .star), (.plus, .zeroOrOne): 149 | return true 150 | case (.star, .zeroOrOne): 151 | return true 152 | default: 153 | return true 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /Sources/SPARQLSyntax/Query.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum SelectProjection : Equatable, Hashable { 4 | case star 5 | case variables([String]) 6 | } 7 | 8 | extension SelectProjection: Codable { 9 | private enum CodingKeys: String, CodingKey { 10 | case type 11 | case variables 12 | } 13 | 14 | public init(from decoder: Decoder) throws { 15 | let container = try decoder.container(keyedBy: CodingKeys.self) 16 | let type = try container.decode(String.self, forKey: .type) 17 | switch type { 18 | case "variables": 19 | let vars = try container.decode([String].self, forKey: .variables) 20 | self = .variables(vars) 21 | default: 22 | self = .star 23 | } 24 | } 25 | 26 | public func encode(to encoder: Encoder) throws { 27 | var container = encoder.container(keyedBy: CodingKeys.self) 28 | switch self { 29 | case let .variables(vars): 30 | try container.encode("variables", forKey: .type) 31 | try container.encode(vars, forKey: .variables) 32 | case .star: 33 | try container.encode("star", forKey: .type) 34 | } 35 | } 36 | } 37 | 38 | public enum QueryForm : Equatable, Hashable { 39 | case select(SelectProjection) 40 | case ask 41 | case construct([TriplePattern]) 42 | case describe([Node]) 43 | } 44 | 45 | extension QueryForm: Codable { 46 | private enum CodingKeys: String, CodingKey { 47 | case type 48 | case projection 49 | case patterns 50 | case nodes 51 | } 52 | 53 | public init(from decoder: Decoder) throws { 54 | let container = try decoder.container(keyedBy: CodingKeys.self) 55 | let type = try container.decode(String.self, forKey: .type) 56 | switch type { 57 | case "select": 58 | let p = try container.decode(SelectProjection.self, forKey: .projection) 59 | self = .select(p) 60 | case "ask": 61 | self = .ask 62 | case "construct": 63 | let tps = try container.decode([TriplePattern].self, forKey: .patterns) 64 | self = .construct(tps) 65 | case "describe": 66 | let nodes = try container.decode([Node].self, forKey: .nodes) 67 | self = .describe(nodes) 68 | default: 69 | throw SPARQLSyntaxError.serializationError("Unexpected query form type '\(type)' found") 70 | } 71 | } 72 | 73 | public func encode(to encoder: Encoder) throws { 74 | var container = encoder.container(keyedBy: CodingKeys.self) 75 | switch self { 76 | case let .select(p): 77 | try container.encode("select", forKey: .type) 78 | try container.encode(p, forKey: .projection) 79 | case .ask: 80 | try container.encode("ask", forKey: .type) 81 | case let .construct(tps): 82 | try container.encode("construct", forKey: .type) 83 | try container.encode(tps, forKey: .patterns) 84 | case let .describe(nodes): 85 | try container.encode("describe", forKey: .type) 86 | try container.encode(nodes, forKey: .nodes) 87 | } 88 | } 89 | } 90 | 91 | public protocol DatasetProtocol { 92 | var defaultGraphs: [Term] { get } 93 | var namedGraphs: [Term] { get } 94 | var isEmpty : Bool { get } 95 | } 96 | 97 | public struct Dataset : DatasetProtocol, Codable, Equatable, Hashable { 98 | public var defaultGraphs: [Term] 99 | public var namedGraphs: [Term] 100 | 101 | public init(defaultGraphs: [Term]? = nil, namedGraphs: [Term]? = nil) { 102 | self.defaultGraphs = defaultGraphs ?? [] 103 | self.namedGraphs = namedGraphs ?? [] 104 | } 105 | 106 | public var isEmpty : Bool { 107 | return defaultGraphs.count == 0 && namedGraphs.count == 0 108 | } 109 | } 110 | 111 | public struct Query : Codable, Hashable, Equatable { 112 | public var base: String? 113 | public var form: QueryForm 114 | public var algebra: Algebra 115 | public var dataset: Dataset? 116 | 117 | public init(form: QueryForm, algebra: Algebra, dataset: Dataset? = nil, base: String? = nil) throws { 118 | self.base = base 119 | self.form = form 120 | self.algebra = algebra 121 | self.dataset = dataset 122 | 123 | switch form { 124 | case .select(.star): 125 | if algebra.isAggregation { 126 | throw SPARQLSyntaxError.parsingError("Aggregation queries cannot use a `SELECT *`") 127 | } 128 | case .select(.variables(let vars)): 129 | if algebra.isAggregation { 130 | var a = algebra 131 | if case .project(let b, _) = a { 132 | a = b 133 | } 134 | if !(Set(vars).isSubset(of: a.projectableVariables)) { 135 | throw SPARQLSyntaxError.parsingError("Cannot project non-grouped variable(s) \(vars) in aggregation query. Projectable variables are: \(a.projectableVariables)") 136 | } 137 | } 138 | 139 | let vset = Set(vars) 140 | if vars.count != vset.count { 141 | // Parsing might allow a SELECT(vars) query form where there are repeated variables, 142 | // but the SPARQL algebra says that projection is over a *set* of variables, so we 143 | // unique the variables here (while preserving order): 144 | var seen = Set() 145 | var uniqVars = [String]() 146 | for v in vars { 147 | if !seen.contains(v) { 148 | uniqVars.append(v) 149 | seen.insert(v) 150 | } 151 | } 152 | self.form = .select(.variables(uniqVars)) 153 | } 154 | default: 155 | break 156 | } 157 | } 158 | } 159 | 160 | 161 | public extension Query { 162 | func serialize(depth: Int=0) -> String { 163 | let indent = String(repeating: " ", count: (depth*2)) 164 | let algebra = self.algebra 165 | var d = "\(indent)Query\n" 166 | if let dataset = self.dataset { 167 | if !dataset.isEmpty { 168 | d += "\(indent) Dataset\n" 169 | for g in dataset.defaultGraphs { 170 | d += "\(indent) Default graph: \(g)\n" 171 | } 172 | for g in dataset.namedGraphs { 173 | d += "\(indent) Named graph: \(g)\n" 174 | } 175 | } 176 | } 177 | switch self.form { 178 | case .construct(let triples): 179 | d += "\(indent) Construct\n" 180 | d += "\(indent) Algebra\n" 181 | d += algebra.serialize(depth: depth+3) 182 | d += "\(indent) Template\n" 183 | for t in triples { 184 | d += "\(indent) \(t)\n" 185 | } 186 | case .describe(let nodes): 187 | let expressions = nodes.map { "\($0)" } 188 | d += "\(indent) Describe { \(expressions.joined(separator: ", ")) }\n" 189 | d += algebra.serialize(depth: depth+2) 190 | case .ask: 191 | d += "\(indent) Ask\n" 192 | d += algebra.serialize(depth: depth+2) 193 | case .select(.star): 194 | d += "\(indent) Select { * }\n" 195 | d += algebra.serialize(depth: depth+2) 196 | case .select(.variables(let v)): 197 | d += "\(indent) Select { \(v.map { "?\($0)" }.joined(separator: ", ")) }\n" 198 | d += algebra.serialize(depth: depth+2) 199 | } 200 | return d 201 | } 202 | } 203 | 204 | public extension Query { 205 | var inscope: Set { 206 | return self.algebra.inscope 207 | } 208 | 209 | var necessarilyBound: Set { 210 | switch self.form { 211 | case .select(.variables(let v)): 212 | return self.algebra.necessarilyBound.intersection(v) 213 | case .select(.star): 214 | return self.algebra.necessarilyBound 215 | case .ask, .construct(_), .describe(_): 216 | return Set() 217 | } 218 | } 219 | 220 | var projectedVariables: [String] { 221 | switch self.form { 222 | case .select(.variables(let v)): 223 | return v 224 | case .select(.star): 225 | return self.inscope.sorted() 226 | case .ask, .construct(_), .describe(_): 227 | return [] 228 | } 229 | } 230 | } 231 | 232 | 233 | public extension Query { 234 | func replace(_ map: [String:Term]) throws -> Query { 235 | let nodes = map.mapValues { Node.bound($0) } 236 | return try self.replace(nodes) 237 | } 238 | 239 | func replace(_ map: [String:Node]) throws -> Query { 240 | let algebra = try self.algebra.replace(map) 241 | return try Query(form: self.form, algebra: algebra, dataset: self.dataset, base: self.base) 242 | } 243 | 244 | func replace(_ map: (Expression) throws -> Expression?) throws -> Query { 245 | let algebra = try self.algebra.replace(map) 246 | return try Query(form: self.form, algebra: algebra, dataset: self.dataset, base: self.base) 247 | } 248 | 249 | func replace(_ map: (Algebra) throws -> Algebra?) throws -> Query { 250 | let algebra = try self.algebra.replace(map) 251 | return try Query(form: self.form, algebra: algebra, dataset: self.dataset, base: self.base) 252 | } 253 | 254 | func rewrite(_ map: (Algebra) throws -> RewriteStatus) throws -> Query { 255 | let algebra = try self.algebra.rewrite(map) 256 | return try Query(form: self.form, algebra: algebra, dataset: self.dataset, base: self.base) 257 | } 258 | } 259 | 260 | enum SPARQLResultError: Error { 261 | case compatabilityError(String) 262 | } 263 | 264 | public struct SPARQLResultSolution: Hashable, Comparable, Sequence, CustomStringConvertible { 265 | public typealias TermType = T 266 | public private(set) var bindings: [String: T] 267 | 268 | public init(bindings: [String: T]) { 269 | self.bindings = bindings 270 | } 271 | 272 | public var keys: [String] { return Array(self.bindings.keys) } 273 | 274 | public func join(_ rhs: Self) -> Self? { 275 | let lvars = Set(bindings.keys) 276 | let rvars = Set(rhs.bindings.keys) 277 | let shared = lvars.intersection(rvars) 278 | for key in shared { 279 | guard bindings[key] == rhs.bindings[key] else { return nil } 280 | } 281 | var b = bindings 282 | for (k, v) in rhs.bindings { 283 | b[k] = v 284 | } 285 | 286 | let result = Self(bindings: b) 287 | // print("]]]] \(self) |><| \(rhs) ==> \(result)") 288 | return result 289 | } 290 | 291 | public func projected(variables: Set) -> Self { 292 | var bindings = [String:TermType]() 293 | for name in variables { 294 | if let term = self[name] { 295 | bindings[name] = term 296 | } 297 | } 298 | return Self(bindings: bindings) 299 | } 300 | 301 | public subscript(key: Node) -> TermType? { 302 | get { 303 | switch key { 304 | case .variable(let name, _): 305 | return self.bindings[name] 306 | default: 307 | return nil 308 | } 309 | } 310 | 311 | set(value) { 312 | if case .variable(let name, _) = key { 313 | self.bindings[name] = value 314 | } 315 | } 316 | } 317 | 318 | public subscript(key: String) -> TermType? { 319 | get { 320 | return bindings[key] 321 | } 322 | 323 | set(value) { 324 | bindings[key] = value 325 | } 326 | } 327 | 328 | public mutating func extend(variable: String, value: TermType) throws { 329 | if let existing = self.bindings[variable] { 330 | if existing != value { 331 | throw SPARQLResultError.compatabilityError("Cannot extend solution mapping due to existing incompatible term value") 332 | } 333 | } 334 | self.bindings[variable] = value 335 | } 336 | 337 | public func extended(variable: String, value: TermType) -> Self? { 338 | var b = bindings 339 | if let existing = b[variable] { 340 | if existing != value { 341 | print("*** cannot extend result with new term: (\(variable) <- \(value); \(self)") 342 | return nil 343 | } 344 | } 345 | b[variable] = value 346 | return Self(bindings: b) 347 | } 348 | 349 | public var description: String { 350 | let pairs = bindings.sorted { $0.0 < $1.0 }.map { "\($0): \($1)" }.joined(separator: ", ") 351 | return "Result[\(pairs)]" 352 | } 353 | 354 | public func description(orderedBy variables: [String]) -> String { 355 | let order = Dictionary(uniqueKeysWithValues: variables.enumerated().map { ($0.element, $0.offset) }) 356 | let pairs = bindings.sorted { order[$0.0, default: Int.max] < order[$1.0, default: Int.max] }.map { "\($0): \($1)" }.joined(separator: ", ") 357 | return "Result[\(pairs)]" 358 | } 359 | 360 | public func makeIterator() -> DictionaryIterator { 361 | let i = bindings.makeIterator() 362 | return i 363 | } 364 | 365 | public func removing(variables: Set) -> Self { 366 | var bindings = [String: T]() 367 | for (k, v) in self.bindings { 368 | if !variables.contains(k) { 369 | bindings[k] = v 370 | } 371 | } 372 | return Self(bindings: bindings) 373 | } 374 | 375 | public static func < (lhs: SPARQLResultSolution, rhs: SPARQLResultSolution) -> Bool { 376 | let keys = Set(lhs.keys + rhs.keys).sorted() 377 | for key in keys { 378 | if let l = lhs[key], let r = rhs[key] { 379 | if l == r { 380 | continue 381 | } 382 | return l < r 383 | } else if let _ = lhs[key] { 384 | return false 385 | } else { 386 | return true 387 | } 388 | } 389 | return false 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /Sources/SPARQLSyntax/RDFPatterns.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // SPARQLSyntax 4 | // 5 | // Created by Gregory Todd Williams on 4/28/18. 6 | // 7 | 8 | import Foundation 9 | 10 | public protocol TermPattern { 11 | associatedtype GroundType: Sequence 12 | static var groundKeyPaths: [KeyPath] { get } 13 | static var groundKeyNames: [String] { get } 14 | var isGround: Bool { get } 15 | var ground: GroundType? { get } 16 | func matches(_ statement: GroundType) -> Bool 17 | func makeIterator() -> IndexingIterator<[Node]> 18 | var bindingAllVariables: Self { get } 19 | } 20 | 21 | extension TermPattern { 22 | public var variables: Set { 23 | let vars = self.makeIterator().compactMap { (n) -> String? in 24 | switch n { 25 | case .variable(let name, binding: _): 26 | return name 27 | default: 28 | return nil 29 | } 30 | } 31 | return Set(vars) 32 | } 33 | 34 | public var isGround: Bool { 35 | for n in makeIterator() { 36 | if case .variable = n { 37 | return false 38 | } 39 | } 40 | return true 41 | } 42 | 43 | public func bindings(for statement: GroundType) -> [String:Term]? { 44 | guard self.matches(statement) else { return nil } 45 | var d = [String: Term]() 46 | let patternType = type(of: self) 47 | for (node, path) in zip(self.makeIterator(), patternType.groundKeyPaths) { 48 | switch node { 49 | case .variable(let name, true): 50 | let term = statement[keyPath: path] 51 | d[name] = term 52 | default: 53 | break 54 | } 55 | } 56 | return d 57 | } 58 | } 59 | 60 | public struct TriplePattern: Hashable, Equatable, Codable, TermPattern, CustomStringConvertible { 61 | public var subject: Node 62 | public var predicate: Node 63 | public var object: Node 64 | public typealias GroundType = Triple 65 | public static var groundKeyPaths: [KeyPath] = [\Triple.subject, \Triple.predicate, \Triple.object] 66 | public static var groundKeyNames = ["subject", "predicate", "object"] 67 | 68 | public init(subject: Node, predicate: Node, object: Node) { 69 | self.subject = subject 70 | self.predicate = predicate 71 | self.object = object 72 | } 73 | 74 | public var description: String { 75 | return "\(subject) \(predicate) \(object) ." 76 | } 77 | 78 | public func bind(_ variable: String, to replacement: Node) -> TriplePattern { 79 | let subject = self.subject.bind(variable, to: replacement) 80 | let predicate = self.predicate.bind(variable, to: replacement) 81 | let object = self.object.bind(variable, to: replacement) 82 | return TriplePattern(subject: subject, predicate: predicate, object: object) 83 | } 84 | 85 | public var bindingAllVariables: TriplePattern { 86 | let nodes = self.map { (n) -> Node in 87 | switch n { 88 | case .variable(let name, binding: false): 89 | return .variable(name, binding: true) 90 | default: 91 | return n 92 | } 93 | } 94 | let s = nodes[0] 95 | let p = nodes[1] 96 | let o = nodes[2] 97 | return TriplePattern(subject: s, predicate: p, object: o) 98 | } 99 | 100 | public static var all: TriplePattern { 101 | return TriplePattern( 102 | subject: .variable("subject", binding: true), 103 | predicate: .variable("predicate", binding: true), 104 | object: .variable("object", binding: true) 105 | ) 106 | } 107 | } 108 | 109 | extension TriplePattern { 110 | public subscript(_ position: Triple.Position) -> Node { 111 | switch position { 112 | case .subject: 113 | return self.subject 114 | case .predicate: 115 | return self.predicate 116 | case .object: 117 | return self.object 118 | } 119 | } 120 | } 121 | 122 | extension TriplePattern: Sequence { 123 | public func makeIterator() -> IndexingIterator<[Node]> { 124 | return [subject, predicate, object].makeIterator() 125 | } 126 | } 127 | 128 | extension TriplePattern { 129 | public var ground: GroundType? { 130 | guard case let .bound(s) = subject, case let .bound(p) = predicate, case let .bound(o) = object else { return nil } 131 | return Triple(subject: s, predicate: p, object: o) 132 | } 133 | 134 | public func replace(_ map: (Node) throws -> Node?) throws -> TriplePattern { 135 | var nodes = [subject, predicate, object] 136 | for (i, node) in nodes.enumerated() { 137 | if let n = try map(node) { 138 | nodes[i] = n 139 | } 140 | } 141 | return TriplePattern(subject: nodes[0], predicate: nodes[1], object: nodes[2]) 142 | } 143 | 144 | public func matches(_ triple: Triple) -> Bool { 145 | var matched = [String:Term]() 146 | for (node, term) in zip(self, triple) { 147 | switch node { 148 | case .variable(let name, binding: true): 149 | if let t = matched[name] { 150 | if t != term { 151 | return false 152 | } 153 | } else { 154 | matched[name] = term 155 | } 156 | case .bound(let t) where t != term: 157 | return false 158 | default: 159 | continue 160 | } 161 | } 162 | return true 163 | } 164 | } 165 | 166 | public struct QuadPattern: Hashable, Equatable, Codable, TermPattern, CustomStringConvertible { 167 | public var subject: Node 168 | public var predicate: Node 169 | public var object: Node 170 | public var graph: Node 171 | public typealias GroundType = Quad 172 | public static var keyPaths: [WritableKeyPath] = [\.subject, \.predicate, \.object, \.graph] 173 | public static var groundKeyPaths: [KeyPath] = [\Quad.subject, \Quad.predicate, \Quad.object, \Quad.graph] 174 | public static var groundKeyNames = ["subject", "predicate", "object", "graph"] 175 | 176 | public init(triplePattern tp: TriplePattern, graph: Node) { 177 | self.subject = tp.subject 178 | self.predicate = tp.predicate 179 | self.object = tp.object 180 | self.graph = graph 181 | } 182 | 183 | public init(subject: Node, predicate: Node, object: Node, graph: Node) { 184 | self.subject = subject 185 | self.predicate = predicate 186 | self.object = object 187 | self.graph = graph 188 | } 189 | public var description: String { 190 | return "\(subject) \(predicate) \(object) \(graph)." 191 | } 192 | 193 | public func bind(_ variable: String, to replacement: Node) -> QuadPattern { 194 | let subject = self.subject.bind(variable, to: replacement) 195 | let predicate = self.predicate.bind(variable, to: replacement) 196 | let object = self.object.bind(variable, to: replacement) 197 | let graph = self.graph.bind(variable, to: replacement) 198 | return QuadPattern(subject: subject, predicate: predicate, object: object, graph: graph) 199 | } 200 | 201 | public func expand(_ values: [String:Term]) -> QuadPattern { 202 | var qp = self 203 | for p in QuadPattern.keyPaths { 204 | let n = self[keyPath: p] 205 | if case .variable(let name, _) = n { 206 | if let term = values[name] { 207 | qp[keyPath: p] = .bound(term) 208 | } 209 | } 210 | } 211 | return qp 212 | } 213 | 214 | public var bindingAllVariables: QuadPattern { 215 | let nodes = self.map { (n) -> Node in 216 | switch n { 217 | case .variable(let name, binding: false): 218 | return .variable(name, binding: true) 219 | default: 220 | return n 221 | } 222 | } 223 | let s = nodes[0] 224 | let p = nodes[1] 225 | let o = nodes[2] 226 | let g = nodes[3] 227 | return QuadPattern(subject: s, predicate: p, object: o, graph: g) 228 | } 229 | 230 | public static var all: QuadPattern { 231 | return QuadPattern( 232 | subject: .variable("subject", binding: true), 233 | predicate: .variable("predicate", binding: true), 234 | object: .variable("object", binding: true), 235 | graph: .variable("graph", binding: true) 236 | ) 237 | } 238 | } 239 | 240 | extension QuadPattern { 241 | public subscript(_ position: Quad.Position) -> Node { 242 | switch position { 243 | case .subject: 244 | return self.subject 245 | case .predicate: 246 | return self.predicate 247 | case .object: 248 | return self.object 249 | case .graph: 250 | return self.graph 251 | } 252 | } 253 | } 254 | 255 | extension QuadPattern: Sequence { 256 | public func makeIterator() -> IndexingIterator<[Node]> { 257 | return [subject, predicate, object, graph].makeIterator() 258 | } 259 | } 260 | 261 | extension QuadPattern { 262 | public var ground: GroundType? { 263 | guard case let .bound(s) = subject, case let .bound(p) = predicate, case let .bound(o) = object, case let .bound(g) = graph else { return nil } 264 | return Quad(subject: s, predicate: p, object: o, graph: g) 265 | } 266 | 267 | public func replace(_ map: (Node) throws -> Node?) throws -> QuadPattern { 268 | var nodes = [subject, predicate, object, graph] 269 | for (i, node) in nodes.enumerated() { 270 | if let n = try map(node) { 271 | nodes[i] = n 272 | } 273 | } 274 | return QuadPattern(subject: nodes[0], predicate: nodes[1], object: nodes[2], graph: nodes[3]) 275 | } 276 | 277 | public func matches(_ quad: Quad) -> Bool { 278 | var matched = [String:Term]() 279 | for (node, term) in zip(self, quad) { 280 | switch node { 281 | case .variable(let name, binding: true): 282 | if let t = matched[name] { 283 | if t != term { 284 | return false 285 | } 286 | } else { 287 | matched[name] = term 288 | } 289 | case .bound(let t) where t != term: 290 | return false 291 | default: 292 | continue 293 | } 294 | } 295 | return true 296 | } 297 | } 298 | 299 | public struct BGP { 300 | var patterns: [TriplePattern] 301 | public init(_ patterns: [TriplePattern]) { 302 | self.patterns = patterns 303 | } 304 | 305 | public var predicateSet: Set { 306 | let nodes = patterns.map { $0.predicate } 307 | let terms = nodes.map { $0.boundTerm }.compactMap { $0 } 308 | return Set(terms) 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /Sources/SPARQLSyntax/RDFSerialization.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol RDFSerializer { 4 | init() 5 | var canonicalMediaType: String { get } 6 | func serialize(_ triples: S) throws -> Data where S.Element == Triple 7 | func serialize(_ triples: S, to: inout T) throws where S.Element == Triple 8 | } 9 | 10 | public protocol PrefixableRDFSerializer { 11 | var prefixes: [String:Term] { get } 12 | mutating func add(name: String, for namespace: String) 13 | } 14 | 15 | public typealias TripleHandler = (Term, Term, Term) -> Void 16 | public typealias QuadHandler = (Term, Term, Term, Term) -> Void 17 | 18 | public protocol RDFPushParser { 19 | init() 20 | var mediaTypes: Set { get } 21 | func parse(string: String, mediaType: String, base: String?, handleTriple: @escaping TripleHandler) throws -> Int 22 | func parseFile(_ filename: String, mediaType: String, base: String?, handleTriple: @escaping TripleHandler) throws -> Int 23 | 24 | func parse(string: String, mediaType: String, defaultGraph: Term, base: String?, handleQuad: @escaping QuadHandler) throws -> Int 25 | func parseFile(_ filename: String, mediaType: String, defaultGraph: Term, base: String?, handleQuad: @escaping QuadHandler) throws -> Int 26 | } 27 | public typealias RDFParser = RDFPushParser 28 | 29 | public protocol RDFPullParser { 30 | init() 31 | var mediaTypes: Set { get } 32 | func parse(string: String, mediaType: String, base: String?, handleTriple: @escaping TripleHandler) throws -> AnySequence 33 | func parseFile(_ filename: String, mediaType: String, base: String?, handleTriple: @escaping TripleHandler) throws -> AnySequence 34 | 35 | func parse(string: String, mediaType: String, defaultGraph: Term, base: String?, handleQuad: @escaping QuadHandler) throws -> AnySequence 36 | func parseFile(_ filename: String, mediaType: String, defaultGraph: Term, base: String?, handleQuad: @escaping QuadHandler) throws -> AnySequence 37 | } 38 | 39 | -------------------------------------------------------------------------------- /Sources/SPARQLSyntax/SPARQLSyntax.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SPARQLSyntax.swift 3 | // SPARQLSyntax 4 | // 5 | // Created by Gregory Todd Williams on 5/25/18. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum SPARQLSyntaxError: Error, CustomStringConvertible { 11 | case lexicalError(String) 12 | case parsingError(String) 13 | case serializationError(String) 14 | 15 | public var localizedDescription : String { 16 | return self.description 17 | } 18 | 19 | public var description : String { 20 | switch self { 21 | case .lexicalError(let s): 22 | return s 23 | case .parsingError(let s): 24 | return s 25 | case .serializationError(let s): 26 | return s 27 | } 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /Sources/SPARQLSyntax/Window.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Window.swift 3 | // SPARQLSyntax 4 | // 5 | // Created by Gregory Todd Williams on 3/6/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum WindowFunction { 11 | case rowNumber 12 | case rank 13 | case denseRank 14 | case ntile(Int) 15 | case aggregation(Aggregation) 16 | case custom(String, [Expression]) 17 | 18 | public var variables: Set { 19 | switch self { 20 | case .rowNumber, .rank, .denseRank, .ntile(_): 21 | return Set() 22 | case .aggregation(let agg): 23 | return agg.variables 24 | case .custom(_, let args): 25 | var vars = Set() 26 | args.forEach { (e) in 27 | vars.formUnion(e.variables) 28 | } 29 | return vars 30 | } 31 | } 32 | 33 | public var expressions: [Expression]? { 34 | switch self { 35 | case .rowNumber, .rank, .denseRank, .ntile(_): 36 | return nil 37 | case .aggregation(let agg): 38 | if let expr = agg.expression { 39 | return [expr] 40 | } 41 | return nil 42 | case .custom(_, let args): 43 | return args 44 | } 45 | } 46 | } 47 | 48 | extension WindowFunction : Hashable, Codable { 49 | private enum CodingKeys: String, CodingKey { 50 | case type 51 | case aggregation 52 | case ntile 53 | case iri 54 | case arguments 55 | } 56 | 57 | public init(from decoder: Decoder) throws { 58 | let container = try decoder.container(keyedBy: CodingKeys.self) 59 | let type = try container.decode(String.self, forKey: .type) 60 | switch type { 61 | case "ROW_NUMBER": 62 | self = .rowNumber 63 | case "RANK": 64 | self = .rank 65 | case "DENSE_RANK": 66 | self = .rank 67 | case "NTILE": 68 | let n = try container.decode(Int.self, forKey: .ntile) 69 | self = .ntile(n) 70 | case "AGGREGATION": 71 | let agg = try container.decode(Aggregation.self, forKey: .aggregation) 72 | self = .aggregation(agg) 73 | case "CUSTOM": 74 | let iri = try container.decode(String.self, forKey: .iri) 75 | let args = try container.decode([Expression].self, forKey: .arguments) 76 | self = .custom(iri, args) 77 | default: 78 | throw SPARQLSyntaxError.serializationError("Unexpected window function type '\(type)' found") 79 | } 80 | } 81 | 82 | public func encode(to encoder: Encoder) throws { 83 | var container = encoder.container(keyedBy: CodingKeys.self) 84 | switch self { 85 | case .rowNumber: 86 | try container.encode("ROW_NUMBER", forKey: .type) 87 | case .rank: 88 | try container.encode("RANK", forKey: .type) 89 | case .denseRank: 90 | try container.encode("DENSE_RANK", forKey: .type) 91 | case .ntile(let n): 92 | try container.encode("NTILE", forKey: .type) 93 | try container.encode(n, forKey: .ntile) 94 | case .aggregation(let agg): 95 | try container.encode("AGGREGATION", forKey: .type) 96 | try container.encode(agg, forKey: .aggregation) 97 | case let .custom(iri, args): 98 | try container.encode("CUSTOM", forKey: .type) 99 | try container.encode(iri, forKey: .iri) 100 | try container.encode(args, forKey: .arguments) 101 | } 102 | } 103 | } 104 | 105 | extension WindowFunction: CustomStringConvertible { 106 | public var description: String { 107 | switch self { 108 | case .rowNumber: 109 | return "ROW_NUMBER()" 110 | case .rank: 111 | return "RANK()" 112 | case .denseRank: 113 | return "DENSE_RANK()" 114 | case .ntile(let n): 115 | return "NTILE(\(n))" 116 | case .aggregation(let agg): 117 | return agg.description 118 | case let .custom(iri, args): 119 | return "<\(iri)>(\(args.map { $0.description }.joined(separator: ", "))" 120 | } 121 | } 122 | } 123 | 124 | public extension WindowFunction { 125 | func replace(_ map: [String:Term]) throws -> WindowFunction { 126 | let nodes = map.mapValues { Node.bound($0) } 127 | return try self.replace(nodes) 128 | } 129 | 130 | func replace(_ map: [String:Node]) throws -> WindowFunction { 131 | switch self { 132 | case .rank, .denseRank, .rowNumber, .ntile(_): 133 | return self 134 | case .aggregation(let agg): 135 | return try .aggregation(agg.replace(map)) 136 | case let .custom(iri, args): 137 | return try .custom(iri, args.map { try $0.replace(map) }) 138 | } 139 | } 140 | 141 | func replace(_ map: (Expression) throws -> Expression?) throws -> WindowFunction { 142 | switch self { 143 | case .rank, .denseRank, .rowNumber, .ntile(_): 144 | return self 145 | case .aggregation(let agg): 146 | return try .aggregation(agg.replace(map)) 147 | case let .custom(iri, args): 148 | return try .custom(iri, args.map { try $0.replace(map) }) 149 | } 150 | } 151 | 152 | func rewrite(_ map: (Expression) throws -> RewriteStatus) throws -> WindowFunction { 153 | switch self { 154 | case .rank, .denseRank, .rowNumber, .ntile(_): 155 | return self 156 | case .aggregation(let agg): 157 | return try .aggregation(agg.rewrite(map)) 158 | case let .custom(iri, args): 159 | return try .custom(iri, args.map { try $0.rewrite(map) }) 160 | } 161 | } 162 | } 163 | 164 | public struct WindowFrame: Hashable, Codable { 165 | public enum FrameBound: Hashable { 166 | case current 167 | case unbound 168 | case preceding(Expression) 169 | case following(Expression) 170 | } 171 | public enum FrameType: Hashable { 172 | case rows 173 | case range 174 | } 175 | public var type: FrameType 176 | public var from: FrameBound 177 | public var to: FrameBound 178 | 179 | public init(type: FrameType, from: FrameBound, to: FrameBound) { 180 | self.type = type 181 | self.from = from 182 | self.to = to 183 | } 184 | } 185 | 186 | extension WindowFrame.FrameBound : Codable { 187 | private enum CodingKeys: String, CodingKey { 188 | case type 189 | case expression 190 | } 191 | 192 | public init(from decoder: Decoder) throws { 193 | let container = try decoder.container(keyedBy: CodingKeys.self) 194 | let type = try container.decode(String.self, forKey: .type) 195 | switch type { 196 | case "current": 197 | self = .current 198 | case "unbound": 199 | self = .unbound 200 | case "preceding": 201 | let expr = try container.decode(Expression.self, forKey: .expression) 202 | self = .preceding(expr) 203 | case "following": 204 | let expr = try container.decode(Expression.self, forKey: .expression) 205 | self = .following(expr) 206 | default: 207 | throw SPARQLSyntaxError.serializationError("Unexpected window frame bound type '\(type)' found") 208 | } 209 | } 210 | 211 | public func encode(to encoder: Encoder) throws { 212 | var container = encoder.container(keyedBy: CodingKeys.self) 213 | switch self { 214 | case .current: 215 | try container.encode("current", forKey: .type) 216 | case .unbound: 217 | try container.encode("unbound", forKey: .type) 218 | case .preceding(let expr): 219 | try container.encode("preceding", forKey: .type) 220 | try container.encode(expr, forKey: .expression) 221 | case .following(let expr): 222 | try container.encode("following", forKey: .type) 223 | try container.encode(expr, forKey: .expression) 224 | } 225 | } 226 | } 227 | 228 | extension WindowFrame.FrameType : Codable { 229 | private enum CodingKeys: String, CodingKey { 230 | case type 231 | } 232 | 233 | public init(from decoder: Decoder) throws { 234 | let container = try decoder.container(keyedBy: CodingKeys.self) 235 | let type = try container.decode(String.self, forKey: .type) 236 | switch type { 237 | case "rows": 238 | self = .rows 239 | case "range": 240 | self = .range 241 | default: 242 | throw SPARQLSyntaxError.serializationError("Unexpected window frame type '\(type)' found") 243 | } 244 | } 245 | 246 | public func encode(to encoder: Encoder) throws { 247 | var container = encoder.container(keyedBy: CodingKeys.self) 248 | switch self { 249 | case .rows: 250 | try container.encode("rows", forKey: .type) 251 | case .range: 252 | try container.encode("range", forKey: .type) 253 | } 254 | } 255 | } 256 | 257 | 258 | public struct WindowApplication: Hashable, Codable { 259 | public var windowFunction: WindowFunction 260 | public var comparators: [Algebra.SortComparator] 261 | public var partition: [Expression] 262 | public var frame: WindowFrame 263 | public init(windowFunction: WindowFunction, comparators: [Algebra.SortComparator], partition: [Expression], frame: WindowFrame) { 264 | self.windowFunction = windowFunction 265 | self.comparators = comparators 266 | self.partition = partition 267 | self.frame = frame 268 | } 269 | public var variables: Set { 270 | return windowFunction.variables 271 | } 272 | } 273 | 274 | public extension WindowApplication { 275 | func replace(_ map: [String:Term]) throws -> WindowApplication { 276 | let nodes = map.mapValues { Node.bound($0) } 277 | return try self.replace(nodes) 278 | } 279 | 280 | func replace(_ map: [String:Node]) throws -> WindowApplication { 281 | let cmps = try self.comparators.map { (cmp) in 282 | try Algebra.SortComparator(ascending: cmp.ascending, expression: cmp.expression.replace(map)) 283 | } 284 | let partition = try self.partition.map { (e) in 285 | try e.replace(map) 286 | } 287 | let frame = try self.frame.replace(map) 288 | return WindowApplication( 289 | windowFunction: windowFunction, 290 | comparators: cmps, 291 | partition: partition, 292 | frame: frame 293 | ) 294 | } 295 | 296 | func replace(_ map: (Expression) throws -> Expression?) throws -> WindowApplication { 297 | let cmps = try self.comparators.map { (cmp) in 298 | try Algebra.SortComparator(ascending: cmp.ascending, expression: cmp.expression.replace(map)) 299 | } 300 | let partition = try self.partition.map { (e) in 301 | try e.replace(map) 302 | } 303 | let frame = try self.frame.replace(map) 304 | return WindowApplication( 305 | windowFunction: windowFunction, 306 | comparators: cmps, 307 | partition: partition, 308 | frame: frame 309 | ) 310 | } 311 | 312 | func rewrite(_ map: (Expression) throws -> RewriteStatus) throws -> WindowApplication { 313 | let cmps = try self.comparators.map { (cmp) in 314 | try Algebra.SortComparator(ascending: cmp.ascending, expression: cmp.expression.rewrite(map)) 315 | } 316 | let partition = try self.partition.map { (e) in 317 | try e.rewrite(map) 318 | } 319 | let frame = try self.frame.rewrite(map) 320 | return WindowApplication( 321 | windowFunction: windowFunction, 322 | comparators: cmps, 323 | partition: partition, 324 | frame: frame 325 | ) 326 | } 327 | } 328 | 329 | extension WindowFrame { 330 | func replace(_ map: [String:Term]) throws -> WindowFrame { 331 | let nodes = map.mapValues { Node.bound($0) } 332 | return try self.replace(nodes) 333 | } 334 | 335 | func replace(_ map: [String:Node]) throws -> WindowFrame { 336 | return try WindowFrame( 337 | type: type, 338 | from: from.replace(map), 339 | to: to.replace(map) 340 | ) 341 | } 342 | 343 | func replace(_ map: (Expression) throws -> Expression?) throws -> WindowFrame { 344 | return try WindowFrame( 345 | type: type, 346 | from: from.replace(map), 347 | to: to.replace(map) 348 | ) 349 | } 350 | 351 | func rewrite(_ map: (Expression) throws -> RewriteStatus) throws -> WindowFrame { 352 | let from = try self.from.rewrite(map) 353 | let to = try self.to.rewrite(map) 354 | return WindowFrame( 355 | type: type, 356 | from: from, 357 | to: to 358 | ) 359 | } 360 | } 361 | 362 | extension WindowFrame.FrameBound { 363 | func replace(_ map: [String:Term]) throws -> WindowFrame.FrameBound { 364 | let nodes = map.mapValues { Node.bound($0) } 365 | return try self.replace(nodes) 366 | } 367 | 368 | func replace(_ map: [String:Node]) throws -> WindowFrame.FrameBound { 369 | switch self { 370 | case .current, .unbound: 371 | return self 372 | case .preceding(let e): 373 | return try .preceding(e.replace(map)) 374 | case .following(let e): 375 | return try .following(e.replace(map)) 376 | } 377 | } 378 | 379 | func replace(_ map: (Expression) throws -> Expression?) throws -> WindowFrame.FrameBound { 380 | switch self { 381 | case .current, .unbound: 382 | return self 383 | case .preceding(let e): 384 | return try .preceding(e.replace(map)) 385 | case .following(let e): 386 | return try .following(e.replace(map)) 387 | } 388 | } 389 | 390 | func rewrite(_ map: (Expression) throws -> RewriteStatus) throws -> WindowFrame.FrameBound { 391 | switch self { 392 | case .current, .unbound: 393 | return self 394 | case .preceding(let e): 395 | let expr = try e.rewrite(map) 396 | return .preceding(expr) 397 | case .following(let e): 398 | let expr = try e.rewrite(map) 399 | return .following(expr) 400 | } 401 | } 402 | } 403 | 404 | extension WindowFrame: CustomStringConvertible { 405 | public var description: String { 406 | // case current 407 | // case unbound 408 | // case preceding(Expression) 409 | // case following(Expression) 410 | return "\(type) BETWEEN \(from) TO \(to)" 411 | } 412 | } 413 | 414 | extension WindowApplication: CustomStringConvertible { 415 | public var description: String { 416 | let f = self.windowFunction.description 417 | let frame = self.frame 418 | let order = self.comparators 419 | let groups = self.partition 420 | 421 | var parts = [String]() 422 | switch (frame.from, frame.to) { 423 | case (.unbound, .unbound): 424 | return "\(f) OVER (PARTITION BY \(groups) ORDER BY \(order))" 425 | default: 426 | parts.append(frame.description) 427 | } 428 | 429 | if !groups.isEmpty { 430 | parts.append("PARTITION BY \(groups)") 431 | } 432 | 433 | if !order.isEmpty { 434 | parts.append("ORDER BY \(order)") 435 | } 436 | 437 | return "\(f) OVER (\(parts.joined(separator: " ")))" 438 | } 439 | } 440 | 441 | 442 | 443 | extension WindowFunction { 444 | public func sparqlTokens() throws -> AnySequence { 445 | var tokens = [SPARQLToken]() 446 | switch self { 447 | case .rank: 448 | tokens.append(.keyword("RANK")) 449 | tokens.append(.lparen) 450 | tokens.append(.rparen) 451 | case .denseRank: 452 | tokens.append(.keyword("DENSE_RANK")) 453 | tokens.append(.lparen) 454 | tokens.append(.rparen) 455 | case .rowNumber: 456 | tokens.append(.keyword("ROW_NUMBER")) 457 | tokens.append(.lparen) 458 | tokens.append(.rparen) 459 | case .ntile(let n): 460 | tokens.append(.keyword("NTILE")) 461 | tokens.append(.lparen) 462 | tokens.append(.integer("\(n)")) 463 | tokens.append(.rparen) 464 | case .aggregation(let agg): 465 | return try agg.sparqlTokens() 466 | case let .custom(iri, args): 467 | let t = Term(iri: iri) 468 | tokens.append(contentsOf: t.sparqlTokens) 469 | tokens.append(.lparen) 470 | let at = try args.map { try $0.sparqlTokens() } 471 | let j = at.joined(separator: [.comma]) 472 | tokens.append(contentsOf: j) 473 | tokens.append(.rparen) 474 | } 475 | return AnySequence(tokens) 476 | } 477 | } 478 | 479 | extension WindowFrame { 480 | public func sparqlTokens() throws -> AnySequence { 481 | var tokens = [SPARQLToken]() 482 | switch (from, to) { 483 | case (.unbound, .unbound): 484 | return AnySequence(tokens) 485 | default: 486 | break 487 | } 488 | 489 | switch type { 490 | case .range: 491 | tokens.append(.keyword("RANGE")) 492 | case .rows: 493 | tokens.append(.keyword("ROWS")) 494 | } 495 | 496 | tokens.append(.keyword("BETWEEN")) 497 | try tokens.append(contentsOf: from.sparqlTokens()) 498 | tokens.append(.keyword("AND")) 499 | try tokens.append(contentsOf: to.sparqlTokens()) 500 | return AnySequence(tokens) 501 | } 502 | } 503 | 504 | extension WindowFrame.FrameBound { 505 | public func sparqlTokens() throws -> AnySequence { 506 | var tokens = [SPARQLToken]() 507 | switch self { 508 | case .unbound: 509 | tokens.append(.keyword("UNBOUNDED")) 510 | case .current: 511 | tokens.append(.keyword("CURRENT")) 512 | tokens.append(.keyword("ROW")) 513 | case .following(let e): 514 | try tokens.append(contentsOf: e.sparqlTokens()) 515 | tokens.append(.keyword("FOLLOWING")) 516 | case .preceding(let e): 517 | try tokens.append(contentsOf: e.sparqlTokens()) 518 | tokens.append(.keyword("PRECEDING")) 519 | } 520 | return AnySequence(tokens) 521 | } 522 | } 523 | 524 | extension WindowApplication { 525 | public func sparqlTokens() throws -> AnySequence { 526 | let frame = self.frame 527 | let order = self.comparators 528 | let groups = self.partition 529 | 530 | var tokens = [SPARQLToken]() 531 | try tokens.append(contentsOf: self.windowFunction.sparqlTokens()) 532 | tokens.append(.keyword("OVER")) 533 | tokens.append(.lparen) 534 | if !groups.isEmpty { 535 | tokens.append(.keyword("PARTITION")) 536 | tokens.append(.keyword("BY")) 537 | for g in groups { 538 | try tokens.append(contentsOf: g.sparqlTokens()) 539 | } 540 | } 541 | if !order.isEmpty { 542 | tokens.append(.keyword("ORDER")) 543 | tokens.append(.keyword("BY")) 544 | for c in order { 545 | try tokens.append(contentsOf: c.sparqlTokens()) 546 | } 547 | } 548 | try tokens.append(contentsOf: frame.sparqlTokens()) 549 | tokens.append(.rparen) 550 | return AnySequence(tokens) 551 | } 552 | } 553 | 554 | -------------------------------------------------------------------------------- /Sources/SPARQLSyntax/XSD.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XSD.swift 3 | // Kineo 4 | // 5 | // Created by Gregory Todd Williams on 4/5/18. 6 | // Copyright © 2018 Gregory Todd Williams. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum NumericValue: CustomStringConvertible, Equatable, Hashable { 12 | case integer(Int) 13 | case decimal(Decimal) 14 | case float(mantissa: Double, exponent: Int) 15 | case double(mantissa: Double, exponent: Int) 16 | 17 | public var value: Double { 18 | switch self { 19 | case .integer(let value): 20 | return Double(value) 21 | case .decimal(let d): 22 | return NSDecimalNumber(decimal:d).doubleValue 23 | // return Double(truncating: d as NSNumber) 24 | case .float(let mantissa, let exponent), .double(let mantissa, let exponent): 25 | return mantissa * pow(10.0, Double(exponent)) 26 | } 27 | } 28 | 29 | public var absoluteValue: NumericValue { 30 | if value >= 0.0 { 31 | return self 32 | } else { 33 | return self * .integer(-1) 34 | } 35 | } 36 | 37 | public var round: NumericValue { 38 | var v = value 39 | v.round(.toNearestOrAwayFromZero) 40 | switch self { 41 | case .decimal(var decimal): 42 | #if os(macOS) 43 | var rounded = Decimal() 44 | NSDecimalRound(&rounded, &decimal, 0, .plain) 45 | return .decimal(rounded) 46 | #else 47 | return .decimal(Decimal(v)) // might lose precision 48 | #endif 49 | case .float: 50 | return .float(mantissa: v, exponent: 0) 51 | case .double: 52 | return .double(mantissa: v, exponent: 0) 53 | default: 54 | return self 55 | } 56 | } 57 | 58 | public var ceil: NumericValue { 59 | var v = value 60 | v.round(.up) 61 | switch self { 62 | case .decimal: 63 | return .decimal(Decimal(v)) 64 | case .float: 65 | return .float(mantissa: v, exponent: 0) 66 | case .double: 67 | return .float(mantissa: v, exponent: 0) 68 | default: 69 | return self 70 | } 71 | } 72 | 73 | public var floor: NumericValue { 74 | var v = value 75 | v.round(.down) 76 | switch self { 77 | case .decimal: 78 | return .decimal(Decimal(v)) 79 | case .float: 80 | return .float(mantissa: v, exponent: 0) 81 | case .double: 82 | return .double(mantissa: v, exponent: 0) 83 | default: 84 | return self 85 | } 86 | } 87 | 88 | public var term: Term { 89 | switch self { 90 | case .integer(let value): 91 | return Term(integer: value) 92 | case .float: 93 | return Term(float: value) 94 | case .decimal: 95 | return Term(decimal: value) 96 | case .double: 97 | return Term(double: value) 98 | } 99 | } 100 | 101 | public var description: String { 102 | switch self { 103 | case .integer(let i): 104 | return "\(i)" 105 | case .decimal(let value): 106 | return "\(value)" 107 | case let .float(m, e): 108 | return "\(m)E\(e)f" 109 | case let .double(m, e): 110 | return "\(m)E\(e)d" 111 | } 112 | } 113 | 114 | public static func += (lhs: inout NumericValue, rhs: NumericValue) { 115 | lhs = lhs + rhs 116 | } 117 | 118 | public static func -= (lhs: inout NumericValue, rhs: NumericValue) { 119 | lhs = lhs - rhs 120 | } 121 | 122 | public static func + (lhs: NumericValue, rhs: NumericValue) -> NumericValue { 123 | let value = lhs.value + rhs.value 124 | return nonDivResultingNumeric(value, lhs, rhs) 125 | } 126 | 127 | public static func - (lhs: NumericValue, rhs: NumericValue) -> NumericValue { 128 | let value = lhs.value - rhs.value 129 | return nonDivResultingNumeric(value, lhs, rhs) 130 | } 131 | 132 | public static func * (lhs: NumericValue, rhs: NumericValue) -> NumericValue { 133 | let value = lhs.value * rhs.value 134 | return nonDivResultingNumeric(value, lhs, rhs) 135 | } 136 | 137 | public static func / (lhs: NumericValue, rhs: NumericValue) -> NumericValue { 138 | let value = lhs.value / rhs.value 139 | return divResultingNumeric(value, lhs, rhs) 140 | } 141 | 142 | public static prefix func - (num: NumericValue) -> NumericValue { 143 | switch num { 144 | case .integer(let value): 145 | return .integer(-value) 146 | case .decimal(let value): 147 | return .decimal(-value) 148 | case .float(let mantissa, let exponent): 149 | return .float(mantissa: -mantissa, exponent: exponent) 150 | case .double(let mantissa, let exponent): 151 | return .double(mantissa: -mantissa, exponent: exponent) 152 | } 153 | } 154 | } 155 | 156 | public extension NumericValue { 157 | static func === (lhs: NumericValue, rhs: NumericValue) -> Bool { 158 | switch (lhs, rhs) { 159 | case (.integer(let l), .integer(let r)) where l == r: 160 | return true 161 | case (.decimal(let l), .decimal(let r)) where l == r: 162 | return true 163 | case let (.float(lm, le), .float(rm, re)) where lm == rm && le == re: 164 | return true 165 | case let (.double(lm, le), .double(rm, re)) where lm == rm && le == re: 166 | return true 167 | default: 168 | return false 169 | } 170 | } 171 | } 172 | 173 | private func nonDivResultingNumeric(_ value: Double, _ lhs: NumericValue, _ rhs: NumericValue) -> NumericValue { 174 | switch (lhs, rhs) { 175 | case (.integer, .integer): 176 | return .integer(Int(value)) 177 | case (.decimal, .decimal): 178 | return .decimal(Decimal(value)) 179 | case (.float, .float): 180 | return .float(mantissa: value, exponent: 0) 181 | case (.double, .double): 182 | return .double(mantissa: value, exponent: 0) 183 | case (.integer, .decimal), (.decimal, .integer): 184 | return .decimal(Decimal(value)) 185 | case (.integer, .float), (.float, .integer), (.decimal, .float), (.float, .decimal): 186 | return .float(mantissa: value, exponent: 0) 187 | default: 188 | return .double(mantissa: value, exponent: 0) 189 | } 190 | } 191 | 192 | private func divResultingNumeric(_ value: Double, _ lhs: NumericValue, _ rhs: NumericValue) -> NumericValue { 193 | switch (lhs, rhs) { 194 | case (.integer, .integer), (.decimal, .decimal): 195 | return .decimal(Decimal(value)) 196 | case (.float, .float): 197 | return .float(mantissa: value, exponent: 0) 198 | case (.double, .double): 199 | return .double(mantissa: value, exponent: 0) 200 | case (.integer, .decimal), (.decimal, .integer): 201 | return .decimal(Decimal(value)) 202 | case (.integer, .float), (.float, .integer), (.decimal, .float), (.float, .decimal): 203 | return .float(mantissa: value, exponent: 0) 204 | default: 205 | return .double(mantissa: value, exponent: 0) 206 | } 207 | } 208 | 209 | -------------------------------------------------------------------------------- /Sources/sparql-parser/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // Kineo 4 | // 5 | // Created by Gregory Todd Williams on 6/24/16. 6 | // Copyright © 2016 Gregory Todd Williams. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SPARQLSyntax 11 | 12 | public struct PeekableIterator : IteratorProtocol { 13 | public typealias Element = T.Element 14 | private var generator: T 15 | private var bufferedElement: Element? 16 | public init(generator: T) { 17 | self.generator = generator 18 | bufferedElement = self.generator.next() 19 | } 20 | 21 | public mutating func next() -> Element? { 22 | let r = bufferedElement 23 | bufferedElement = generator.next() 24 | return r 25 | } 26 | 27 | public func peek() -> Element? { 28 | return bufferedElement 29 | } 30 | 31 | mutating func dropWhile(filter: (Element) -> Bool) { 32 | while bufferedElement != nil { 33 | if !filter(bufferedElement!) { 34 | break 35 | } 36 | _ = next() 37 | } 38 | } 39 | 40 | mutating public func elements() -> [Element] { 41 | var elements = [Element]() 42 | while let e = next() { 43 | elements.append(e) 44 | } 45 | return elements 46 | } 47 | } 48 | 49 | func getCurrentDateSeconds() -> UInt64 { 50 | var startTime: time_t 51 | startTime = time(nil) 52 | return UInt64(startTime) 53 | } 54 | 55 | func getCurrentTime() -> TimeInterval { 56 | return ProcessInfo.processInfo.systemUptime 57 | } 58 | 59 | func warn(_ items: String...) { 60 | for string in items { 61 | fputs(string, stderr) 62 | fputs("\n", stderr) 63 | } 64 | } 65 | 66 | func printSPARQL(_ data: Data, pretty: Bool = false, anonymize: Bool = false, silent: Bool = false, includeComments: Bool = false) throws { 67 | guard let sparql = String(data: data, encoding: .utf8) else { 68 | fatalError("Failed to decode SPARQL query as utf8") 69 | } 70 | let s = SPARQLSerializer(prettyPrint: pretty, anonymize: anonymize) 71 | print(s.reformat(sparql)) 72 | } 73 | 74 | func data(fromFileOrString qfile: String) throws -> (Data, String?) { 75 | if qfile == "-" { 76 | print("reading from stdin...") 77 | var s = "" 78 | while let l = readLine() { 79 | s += l 80 | } 81 | let data = s.data(using: .utf8) 82 | return (data!, nil) 83 | } 84 | let url = URL(fileURLWithPath: qfile) 85 | let data: Data 86 | var base: String? = nil 87 | if case .some(true) = try? url.checkResourceIsReachable() { 88 | data = try Data(contentsOf: url) 89 | base = url.absoluteString 90 | } else { 91 | guard let s = qfile.data(using: .utf8) else { 92 | fatalError("Could not interpret SPARQL query string as UTF-8") 93 | } 94 | data = s 95 | } 96 | return (data, base) 97 | } 98 | 99 | var verbose = true 100 | let argscount = CommandLine.arguments.count 101 | var args = PeekableIterator(generator: CommandLine.arguments.makeIterator()) 102 | guard let pname = args.next() else { fatalError("Missing command name") } 103 | guard argscount > 2 else { 104 | print("Usage: \(pname) [-v] COMMAND [ARGUMENTS]") 105 | print(" \(pname) parse query.rq") 106 | print(" \(pname) lint query.rq") 107 | print(" \(pname) tokens query.rq") 108 | print(" \(pname) anonymize query.rq") 109 | print("") 110 | exit(1) 111 | } 112 | 113 | if let next = args.peek(), next == "-v" { 114 | _ = args.next() 115 | verbose = true 116 | } 117 | 118 | let startTime = getCurrentTime() 119 | let startSecond = getCurrentDateSeconds() 120 | var count = 0 121 | 122 | if let op = args.next() { 123 | if op == "parse" { 124 | var printAlgebra = false 125 | var printSPARQL = false 126 | var pretty = true 127 | if let next = args.peek(), next.lowercased() == "-s" { 128 | _ = args.next() 129 | printSPARQL = true 130 | if next == "-S" { 131 | pretty = true 132 | } 133 | } 134 | if let next = args.peek(), next == "-a" { 135 | _ = args.next() 136 | printAlgebra = true 137 | } 138 | if !printAlgebra && !printSPARQL { 139 | printAlgebra = true 140 | } 141 | 142 | guard let qfile = args.next() else { fatalError("No query file given") } 143 | do { 144 | let (sparql, base) = try data(fromFileOrString: qfile) 145 | guard var p = SPARQLParser(data: sparql, base: base) else { fatalError("Failed to construct SPARQL parser") } 146 | let query = try p.parseQuery() 147 | count = 1 148 | if printAlgebra { 149 | print(query.serialize()) 150 | } 151 | if printSPARQL { 152 | let s = SPARQLSerializer(prettyPrint: pretty) 153 | let tokens = try query.sparqlTokens() 154 | print(s.serialize(tokens)) 155 | } 156 | } catch let e { 157 | warn("*** Failed to parse query: \(e)") 158 | } 159 | } else if op == "tokens" { 160 | var printAlgebra = false 161 | var printSPARQL = false 162 | if let next = args.peek(), next.lowercased() == "-s" { 163 | _ = args.next() 164 | printSPARQL = true 165 | } 166 | if let next = args.peek(), next == "-a" { 167 | _ = args.next() 168 | printAlgebra = true 169 | } 170 | if !printAlgebra && !printSPARQL { 171 | printAlgebra = true 172 | } 173 | 174 | guard let qfile = args.next() else { fatalError("No query file given") } 175 | do { 176 | let (sparql, _) = try data(fromFileOrString: qfile) 177 | let stream = InputStream(data: sparql) 178 | stream.open() 179 | let lexer = try SPARQLLexer(source: stream, includeComments: true) 180 | while let t = lexer.next() { 181 | print("\(t)") 182 | } 183 | } catch let e { 184 | warn("*** Failed to tokenize query: \(e)") 185 | } 186 | } else if let qfile = args.next() { 187 | var pretty = false 188 | var anon = false 189 | if op == "lint" { 190 | pretty = true 191 | } else if op.starts(with: "anon") { 192 | anon = true 193 | } else { 194 | warn("Unrecognized operation: '\(op)'") 195 | exit(1) 196 | } 197 | do { 198 | let (sparql, _) = try data(fromFileOrString: qfile) 199 | try printSPARQL(sparql, pretty: pretty, anonymize: anon, silent: false, includeComments: true) 200 | } catch let e { 201 | warn("*** Failed to lint query: \(e)") 202 | } 203 | } else { 204 | warn("Unrecognized operation: '\(op)'") 205 | exit(1) 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /Sources/sparql-test/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // Kineo 4 | // 5 | // Created by Gregory Todd Williams on 6/24/16. 6 | // Copyright © 2016 Gregory Todd Williams. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SPARQLSyntax 11 | import Rainbow 12 | 13 | enum CLIError : Error { 14 | case error(String) 15 | } 16 | 17 | func string(fromFileOrString qfile: String) throws -> (String, String?) { 18 | let url = URL(fileURLWithPath: qfile) 19 | let string: String 20 | var base: String? = nil 21 | if case .some(true) = try? url.checkResourceIsReachable() { 22 | string = try String(contentsOf: url) 23 | base = url.absoluteString 24 | } else { 25 | string = qfile 26 | } 27 | return (string, base) 28 | } 29 | 30 | func usage() { 31 | let args = CommandLine.arguments 32 | guard let pname = args.first else { fatalError("Missing command name") } 33 | print("Usage: \(pname) [FLAGS] query.rq") 34 | print("") 35 | print("Flags:") 36 | print(" -a Print the query algebra") 37 | print(" -c Read queries from standard input (one per line)") 38 | print(" -d URL-decode the query before parsing") 39 | print("") 40 | exit(1) 41 | } 42 | 43 | func run() throws { 44 | var args = Array(CommandLine.arguments.dropFirst()) 45 | 46 | var stdin = false 47 | var unescape = false 48 | var onePerLine = false 49 | var printAlgebra = false 50 | 51 | while !args.isEmpty && args[0].hasPrefix("-") { 52 | let f = args.remove(at: 0) 53 | if f == "--" { break } 54 | 55 | switch f { 56 | case "-a": 57 | printAlgebra = true 58 | case "-c": 59 | stdin = true 60 | onePerLine = true 61 | case "-d": 62 | unescape = true 63 | case "--help": 64 | usage() 65 | default: 66 | break 67 | } 68 | } 69 | 70 | let unescapeQuery : (String) throws -> String = unescape ? { 71 | let sparql = $0.replacingOccurrences(of: "+", with: " ") 72 | if let s = sparql.removingPercentEncoding { 73 | return s 74 | } else { 75 | let e = CLIError.error("Failed to URL percent decode SPARQL query") 76 | throw e 77 | } 78 | } : { $0 } 79 | 80 | if stdin || args.isEmpty { 81 | if onePerLine { 82 | while let line = readLine() { 83 | let sparql = try unescapeQuery(line) 84 | handleQuery(sparql, printAlgebra) 85 | } 86 | } else { 87 | var sparql = "" 88 | while let line = readLine() { 89 | sparql += line 90 | } 91 | handleQuery(sparql, printAlgebra) 92 | } 93 | } else if let arg = args.first { 94 | let (query, _) = try string(fromFileOrString: arg) 95 | let sparql = try unescapeQuery(query) 96 | handleQuery(sparql, printAlgebra) 97 | } 98 | } 99 | 100 | extension SPARQLSerializer { 101 | public enum HightlightState { 102 | case normal 103 | case highlighted 104 | } 105 | public typealias Highlighter = (String, HightlightState) -> String 106 | public typealias HighlighterMap = [Set>: (String, Highlighter)] 107 | public func reformatHighlightingRanges(_ sparql: String, highlighterMap: HighlighterMap) -> (String, Set?) { 108 | let sparql = prettyPrint ? reformat(sparql) : sparql 109 | 110 | // compute the set of tokens (by their token number) that should be highlighted 111 | var highlightedChars = [(ClosedRange, String, Highlighter)]() 112 | for (ranges, highlighterTuple) in highlighterMap { 113 | var highlightedTokens = Set() 114 | for range in ranges { 115 | for i in range { 116 | highlightedTokens.insert(i) 117 | } 118 | } 119 | 120 | guard let data = sparql.data(using: .utf8) else { 121 | return (sparql, nil) 122 | } 123 | let stream = InputStream(data: data) 124 | 125 | // compute the characters in the sparql string that should be highlighted 126 | stream.open() 127 | var charRanges = [(Int, ClosedRange)]() 128 | do { 129 | let lexer = try SPARQLLexer(source: stream) 130 | while let t = try lexer.getToken() { 131 | if highlightedTokens.contains(t.tokenNumber) { 132 | let range = Int(t.startCharacter)...Int(t.endCharacter) 133 | if charRanges.isEmpty { 134 | charRanges.append((t.tokenNumber, range)) 135 | } else { 136 | let tuple = charRanges.last! 137 | if (tuple.0+1) == t.tokenNumber { 138 | // coalesce 139 | let tuple = charRanges.removeLast() 140 | let r = tuple.1 141 | let rr = r.lowerBound...range.upperBound 142 | charRanges.append((t.tokenNumber, rr)) 143 | } else { 144 | charRanges.append((t.tokenNumber, range)) 145 | } 146 | } 147 | } 148 | } 149 | } catch {} 150 | let name = highlighterTuple.0 151 | highlightedChars.append(contentsOf: charRanges.map { ($0.1, name, highlighterTuple.1) }) 152 | } 153 | 154 | // reverse sort so that we insert color-codes back-to-front and the offsets don't shift underneath us 155 | // note this will not work if the ranges are overlapping 156 | highlightedChars.sort { $0.0.lowerBound > $1.0.lowerBound } 157 | 158 | // for each highlighted character range, replace the substring with one that has .red color codes inserted 159 | var highlighted = sparql 160 | 161 | var names = Set() 162 | for (range, name, highlight) in highlightedChars { 163 | names.insert(highlight(name, .highlighted)) 164 | // TODO: instead of highlighting by subrange replacement, break the string in to all sub-ranges (both highlighted and non-highlighted) 165 | // and call the highlighter for all the ranges and just concatenate them to preduce the result. 166 | let start = sparql.index(sparql.startIndex, offsetBy: range.lowerBound) 167 | let end = sparql.index(sparql.startIndex, offsetBy: range.upperBound) 168 | let stringRange = start..) -> SPARQLSerializer.Highlighter { 177 | return { (s, state) in 178 | if case .highlighted = state { 179 | return s[keyPath: color] 180 | } else { 181 | return s 182 | } 183 | } 184 | } 185 | 186 | func showHighlightedAlgebra(_ sparql : String, _ printAlgebra: Bool, _ predicate : (Algebra) -> (String, Int)?) throws { 187 | var parser = SPARQLParser(string: sparql)! 188 | let q = try parser.parseQuery() 189 | let a = q.algebra 190 | 191 | var names = [Int: String]() 192 | var highlight = [Int: Set>]() 193 | var algebraToTokens = [Algebra: Set>]() 194 | try a.walk { (algebra) in 195 | let ranges = parser.getTokenRange(for: algebra) 196 | 197 | 198 | 199 | // HIGHLIGHT AGGREGATIONS IN THE OUTPUT 200 | if let tuple = predicate(algebra) { 201 | let name = tuple.0 202 | let i = tuple.1 203 | names[i] = name 204 | for r in ranges { 205 | highlight[i, default: []].insert(r) 206 | } 207 | } 208 | 209 | if ranges.isEmpty { 210 | // print("*** No range for algebra: \(algebra.serialize())") 211 | // print("--- \n\(sparql)\n--- \n") 212 | } else { 213 | algebraToTokens[algebra] = ranges 214 | } 215 | } 216 | 217 | let colors : [KeyPath] = [\.red, \.yellow, \.green, \.blue, \.magenta] 218 | if !highlight.isEmpty { 219 | let ser = SPARQLSerializer(prettyPrint: true) 220 | let highlighterMap = Dictionary(uniqueKeysWithValues: highlight.map { 221 | let name = names[$0.key] ?? "\($0.key)" 222 | let highlighter = makeHighlighter(color: colors[$0.key % colors.count]) 223 | return ($0.value, (name, highlighter)) 224 | }) 225 | // let highlighterMap : SPARQLSerializer.HighlighterMap = [highlight: makeHighlighter(color: \.red)] 226 | let (h, names) = ser.reformatHighlightingRanges(sparql, highlighterMap: highlighterMap) 227 | if let names = names { 228 | for name in names.sorted() { 229 | print("- \(name)") 230 | } 231 | } 232 | print("\n\(h)") 233 | if printAlgebra { 234 | print(a.serialize()) 235 | } 236 | } 237 | } 238 | 239 | 240 | func handleQuery(_ sparql : String, _ printAlgebra: Bool) { 241 | do { 242 | guard let data = sparql.data(using: .utf8) else { return } 243 | let stream = InputStream(data: data) 244 | stream.open() 245 | try showHighlightedAlgebra(sparql, printAlgebra) { 246 | switch $0 { 247 | case .innerJoin: 248 | return ("Join", 6) 249 | // case .bgp, .triple: 250 | // return ("BGP", 5) 251 | case .filter: 252 | return ("Filter", 4) 253 | case .path: 254 | return ("Path", 3) 255 | case .order: 256 | return ("Sorting", 2) 257 | // case .leftOuterJoin: 258 | // return ("Optional", 1) 259 | case .slice: 260 | return ("Slicing", 5) 261 | default: 262 | return nil 263 | } 264 | } 265 | } catch let e { 266 | print("*** \(e)") 267 | } 268 | } 269 | 270 | do { 271 | try run() 272 | } catch let e { 273 | print("*** \(e)") 274 | } 275 | -------------------------------------------------------------------------------- /Sources/sparqllint/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // Kineo 4 | // 5 | // Created by Gregory Todd Williams on 6/24/16. 6 | // Copyright © 2016 Gregory Todd Williams. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SPARQLSyntax 11 | 12 | enum CLIError : Error { 13 | case error(String) 14 | } 15 | 16 | struct Config { 17 | var benchmark = false 18 | var stdin = false 19 | var pretty = true 20 | var unescape = false 21 | var onePerLine = false 22 | 23 | func unescape(query : String) throws -> String { 24 | if unescape { 25 | let sparql = query.replacingOccurrences(of: "+", with: " ") 26 | if let s = sparql.removingPercentEncoding { 27 | return s 28 | } else { 29 | let e = CLIError.error("Failed to URL percent decode SPARQL query") 30 | throw e 31 | } 32 | } else { 33 | return query 34 | } 35 | } 36 | } 37 | 38 | func string(fromFileOrString qfile: String) throws -> (String, String?) { 39 | let url = URL(fileURLWithPath: qfile) 40 | let string: String 41 | var base: String? = nil 42 | if case .some(true) = try? url.checkResourceIsReachable() { 43 | string = try String(contentsOf: url) 44 | base = url.absoluteString 45 | } else { 46 | string = qfile 47 | } 48 | return (string, base) 49 | } 50 | 51 | func usage() { 52 | let args = CommandLine.arguments 53 | guard let pname = args.first else { fatalError("Missing command name") } 54 | print("Usage: \(pname) [FLAGS] query.rq") 55 | print("") 56 | print("Flags:") 57 | print(" -c Read queries from standard input (one per line)") 58 | print(" -d URL-decode the query before parsing") 59 | print(" -l Use a concise, one-line output format for the query") 60 | print("") 61 | exit(0) 62 | } 63 | 64 | func reformat(_ sparql: String, config: Config) -> String { 65 | var s = SPARQLSerializer(prettyPrint: config.pretty) 66 | return s.reformat(sparql) 67 | } 68 | 69 | func parseConfig(_ args: inout [String]) -> Config { 70 | var config = Config() 71 | 72 | while !args.isEmpty && args[0].hasPrefix("-") { 73 | let f = args.remove(at: 0) 74 | if f == "--" { break } 75 | 76 | switch f { 77 | case "-b": 78 | config.benchmark = true 79 | case "-c": 80 | config.stdin = true 81 | config.onePerLine = true 82 | case "-l": 83 | config.pretty = false 84 | case "-d": 85 | config.unescape = true 86 | case "--help": 87 | usage() 88 | default: 89 | break 90 | } 91 | } 92 | return config 93 | } 94 | 95 | func run() throws { 96 | var args = Array(CommandLine.arguments.dropFirst()) 97 | let config = parseConfig(&args) 98 | 99 | if config.stdin || args.isEmpty { 100 | if config.onePerLine { 101 | while let line = readLine() { 102 | let sparql = try config.unescape(query: line) 103 | print(reformat(sparql, config: config)) 104 | } 105 | } else { 106 | var sparql = "" 107 | while let line = readLine() { 108 | sparql += line 109 | } 110 | print(reformat(sparql, config: config)) 111 | } 112 | } else if let arg = args.first { 113 | let (query, _) = try string(fromFileOrString: arg) 114 | let max = config.benchmark ? 100_000 : 1 115 | for _ in 0.. () throws -> Void)] { 8 | return [ 9 | ("testExpressionReplacement", testExpressionReplacement), 10 | ("testFilterExpressionReplacement", testFilterExpressionReplacement), 11 | ("testJoinIdentityReplacement", testJoinIdentityReplacement), 12 | ("testNodeBinding", testNodeBinding), 13 | ("testNodeBindingWithProjection", testNodeBindingWithProjection), 14 | ("testReplacement1", testReplacement1), 15 | ("testReplacement2", testReplacement2), 16 | ] 17 | } 18 | } 19 | #endif 20 | 21 | // swiftlint:disable type_body_length 22 | class AlgebraTest: XCTestCase { 23 | 24 | override func setUp() { 25 | super.setUp() 26 | // Put setup code here. This method is called before the invocation of each test method in the class. 27 | } 28 | 29 | override func tearDown() { 30 | // Put teardown code here. This method is called after the invocation of each test method in the class. 31 | super.tearDown() 32 | } 33 | 34 | func testReplacement1() { 35 | let subj: Node = .bound(Term(value: "b", type: .blank)) 36 | let pred: Node = .bound(Term(value: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", type: .iri)) 37 | let obj: Node = .variable("o", binding: true) 38 | let t = TriplePattern(subject: subj, predicate: pred, object: obj) 39 | let algebra: Algebra = .bgp([t]) 40 | 41 | do { 42 | let rewrite = try algebra.replace { (algebra: Algebra) in 43 | switch algebra { 44 | case .bgp(_): 45 | return .joinIdentity 46 | default: 47 | return nil 48 | } 49 | } 50 | 51 | guard case .joinIdentity = rewrite else { 52 | XCTFail("Unexpected rewritten algebra: \(rewrite.serialize())") 53 | return 54 | } 55 | 56 | XCTAssert(true) 57 | } catch { 58 | XCTFail() 59 | } 60 | } 61 | 62 | func testReplacement2() { 63 | let subj: Node = .bound(Term(value: "b", type: .blank)) 64 | let type: Node = .bound(Term(value: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", type: .iri)) 65 | let name: Node = .bound(Term(value: "http://xmlns.com/foaf/0.1/name", type: .iri)) 66 | let vtype: Node = .variable("type", binding: true) 67 | let vname: Node = .variable("name", binding: true) 68 | let t1 = TriplePattern(subject: subj, predicate: type, object: vtype) 69 | let t2 = TriplePattern(subject: subj, predicate: name, object: vname) 70 | let algebra: Algebra = .innerJoin(.bgp([t1]), .triple(t2)) 71 | 72 | do { 73 | let rewrite = try algebra.replace { (algebra: Algebra) in 74 | switch algebra { 75 | case .bgp(_): 76 | return .joinIdentity 77 | default: 78 | return nil 79 | } 80 | } 81 | 82 | guard case .innerJoin(.joinIdentity, .triple(_)) = rewrite else { 83 | XCTFail("Unexpected rewritten algebra: \(rewrite.serialize())") 84 | return 85 | } 86 | 87 | XCTAssert(true) 88 | } catch { 89 | XCTFail() 90 | } 91 | } 92 | 93 | func testJoinIdentityReplacement() { 94 | let subj: Node = .bound(Term(value: "b", type: .blank)) 95 | let name: Node = .bound(Term(value: "http://xmlns.com/foaf/0.1/name", type: .iri)) 96 | let vname: Node = .variable("name", binding: true) 97 | let t = TriplePattern(subject: subj, predicate: name, object: vname) 98 | let algebra: Algebra = .innerJoin(.joinIdentity, .triple(t)) 99 | 100 | do { 101 | let rewrite = try algebra.replace { (algebra: Algebra) in 102 | switch algebra { 103 | case .innerJoin(.joinIdentity, let a), .innerJoin(let a, .joinIdentity): 104 | return a 105 | default: 106 | return nil 107 | } 108 | } 109 | 110 | guard case .triple(_) = rewrite else { 111 | XCTFail("Unexpected rewritten algebra: \(rewrite.serialize())") 112 | return 113 | } 114 | 115 | XCTAssert(true) 116 | } catch { 117 | XCTFail() 118 | } 119 | } 120 | 121 | func testFilterExpressionReplacement() { 122 | let subj: Node = .bound(Term(value: "b", type: .blank)) 123 | let name: Node = .bound(Term(value: "http://xmlns.com/foaf/0.1/name", type: .iri)) 124 | let greg: Node = .bound(Term(value: "Gregory", type: .language("en"))) 125 | let vname: Node = .variable("name", binding: true) 126 | let expr: SPARQLSyntax.Expression = .eq(.node(vname), .node(greg)) 127 | 128 | let t = TriplePattern(subject: subj, predicate: name, object: vname) 129 | let algebra: Algebra = .filter(.triple(t), expr) 130 | 131 | do { 132 | let rewrite = try algebra.replace { (expr: SPARQLSyntax.Expression) in 133 | switch expr { 134 | case .eq(let a, let b): 135 | return .ne(a, b) 136 | default: 137 | return nil 138 | } 139 | } 140 | 141 | guard case .filter(.triple(_), .ne(_, _)) = rewrite else { 142 | XCTFail("Unexpected rewritten algebra: \(rewrite.serialize())") 143 | return 144 | } 145 | 146 | XCTAssert(true) 147 | } catch { 148 | XCTFail() 149 | } 150 | } 151 | 152 | func testExpressionReplacement() { 153 | let greg: Node = .bound(Term(value: "Gregory", type: .language("en"))) 154 | let vname: Node = .variable("name", binding: true) 155 | let expr: SPARQLSyntax.Expression = .eq(.node(vname), .node(greg)) 156 | 157 | do { 158 | let rewrite = try expr.replace { (expr: SPARQLSyntax.Expression) in 159 | switch expr { 160 | case .eq(let a, let b): 161 | return .ne(a, b) 162 | default: 163 | return nil 164 | } 165 | } 166 | 167 | XCTAssertEqual(expr.description, "(?name == \"Gregory\"@en)") 168 | XCTAssertEqual(rewrite.description, "(?name != \"Gregory\"@en)") 169 | } catch { 170 | XCTFail() 171 | } 172 | } 173 | 174 | func testNodeBinding() { 175 | let subj: Node = .bound(Term(value: "b", type: .blank)) 176 | let type: Node = .bound(Term(value: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", type: .iri)) 177 | let name: Node = .bound(Term(value: "http://xmlns.com/foaf/0.1/name", type: .iri)) 178 | let vtype: Node = .variable("type", binding: true) 179 | let vname: Node = .variable("name", binding: true) 180 | let t1 = TriplePattern(subject: subj, predicate: type, object: vtype) 181 | let t2 = TriplePattern(subject: subj, predicate: name, object: vname) 182 | let algebra: Algebra = .project(.innerJoin(.triple(t1), .triple(t2)), ["name", "type"]) 183 | 184 | do { 185 | let rewrite = try algebra.bind("type", to: .bound(Term(value: "http://xmlns.com/foaf/0.1/Person", type: .iri))) 186 | guard case .project(.innerJoin(_, _), let projection) = rewrite else { 187 | XCTFail("Unexpected rewritten algebra: \(rewrite.serialize())") 188 | return 189 | } 190 | XCTAssertEqual(projection, ["name"]) 191 | } catch { 192 | XCTFail() 193 | } 194 | } 195 | 196 | func testNodeBindingWithProjection() { 197 | let subj: Node = .bound(Term(value: "b", type: .blank)) 198 | let type: Node = .bound(Term(value: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", type: .iri)) 199 | let name: Node = .bound(Term(value: "http://xmlns.com/foaf/0.1/name", type: .iri)) 200 | let vtype: Node = .variable("type", binding: true) 201 | let vname: Node = .variable("name", binding: true) 202 | let t1 = TriplePattern(subject: subj, predicate: type, object: vtype) 203 | let t2 = TriplePattern(subject: subj, predicate: name, object: vname) 204 | let algebra: Algebra = .project(.innerJoin(.triple(t1), .triple(t2)), ["name", "type"]) 205 | 206 | do { 207 | let person: Node = .bound(Term(value: "http://xmlns.com/foaf/0.1/Person", type: .iri)) 208 | let rewrite = try algebra.bind("type", to: person, preservingProjection: true) 209 | guard case .project(.extend(.innerJoin(_, _), .node(person), "type"), let projection) = rewrite else { 210 | XCTFail("Unexpected rewritten algebra: \(rewrite.serialize())") 211 | return 212 | } 213 | XCTAssertEqual(projection, ["name", "type"]) 214 | } catch { 215 | XCTFail() 216 | } 217 | } 218 | 219 | func testEncodable() throws { 220 | let subj: Node = .bound(Term(value: "b", type: .blank)) 221 | let type: Node = .bound(Term(value: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", type: .iri)) 222 | let name: Node = .bound(Term(value: "http://xmlns.com/foaf/0.1/name", type: .iri)) 223 | let vtype: Node = .variable("type", binding: true) 224 | let vname: Node = .variable("name", binding: true) 225 | let t1 = TriplePattern(subject: subj, predicate: type, object: vtype) 226 | let t2 = TriplePattern(subject: subj, predicate: name, object: vname) 227 | let algebra: Algebra = .project(.innerJoin(.triple(t1), .triple(t2)), ["name", "type"]) 228 | let je = JSONEncoder() 229 | let jd = JSONDecoder() 230 | let data = try je.encode(algebra) 231 | let r = try jd.decode(Algebra.self, from: data) 232 | XCTAssertEqual(r, algebra) 233 | } 234 | 235 | func testNecessarilyBound() throws { 236 | let subj: Node = .bound(Term(value: "b", type: .blank)) 237 | let type: Node = .bound(Term(value: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", type: .iri)) 238 | let name: Node = .bound(Term(value: "http://xmlns.com/foaf/0.1/name", type: .iri)) 239 | let vtype: Node = .variable("type", binding: true) 240 | let vname: Node = .variable("name", binding: true) 241 | let t1 = TriplePattern(subject: subj, predicate: type, object: vtype) 242 | let t2 = TriplePattern(subject: subj, predicate: name, object: vname) 243 | 244 | let algebra1 = Algebra.project(.innerJoin(.triple(t1), .triple(t2)), ["name", "type"]) 245 | XCTAssertEqual(algebra1.inscope, Set(["type", "name"])) 246 | XCTAssertEqual(algebra1.necessarilyBound, Set(["type", "name"])) 247 | 248 | let algebra2 = Algebra.leftOuterJoin(.triple(t1), .triple(t2), .node(Node(term: Term.trueValue))) 249 | XCTAssertEqual(algebra2.inscope, Set(["type", "name"])) 250 | XCTAssertEqual(algebra2.necessarilyBound, Set(["type"])) 251 | 252 | let algebra3 = Algebra.union(.triple(t1), .triple(t2)) 253 | XCTAssertEqual(algebra3.inscope, Set(["type", "name"])) 254 | XCTAssertEqual(algebra3.necessarilyBound, Set([])) 255 | } 256 | 257 | func testWalk() throws { 258 | let subj: Node = .bound(Term(value: "b", type: .blank)) 259 | let type: Node = .bound(Term(value: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", type: .iri)) 260 | let name: Node = .bound(Term(value: "http://xmlns.com/foaf/0.1/name", type: .iri)) 261 | let vtype: Node = .variable("type", binding: true) 262 | let vname: Node = .variable("name", binding: true) 263 | let t1 = TriplePattern(subject: subj, predicate: type, object: vtype) 264 | let t2 = TriplePattern(subject: subj, predicate: name, object: vname) 265 | 266 | var algebra = Algebra.leftOuterJoin(.bgp([t1]), .triple(t2), .node(Node(term: Term.trueValue))) 267 | algebra = .union(.unionIdentity, algebra) 268 | algebra = .innerJoin(algebra, .joinIdentity) 269 | algebra = .distinct(algebra) 270 | 271 | algebra = .filter(algebra, .node(Node(term: Term.trueValue))) 272 | algebra = .project(algebra, Set(["name"])) 273 | algebra = .order(algebra, [Algebra.SortComparator(ascending: true, expression: Expression(variable: "name"))]) 274 | 275 | var tripleCount = 0 276 | try algebra.walk { (a) in 277 | switch a { 278 | case .triple(_): 279 | tripleCount += 1 280 | default: 281 | break 282 | } 283 | } 284 | XCTAssertEqual(tripleCount, 1) 285 | } 286 | 287 | func testWalkRecursive_AlgebraExpressionAlgebra() throws { 288 | let subj: Node = .bound(Term(value: "b", type: .blank)) 289 | let type: Node = .bound(Term(value: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", type: .iri)) 290 | let name: Node = .bound(Term(value: "http://xmlns.com/foaf/0.1/name", type: .iri)) 291 | let vtype: Node = .variable("type", binding: true) 292 | let vname: Node = .variable("name", binding: true) 293 | let t1 = TriplePattern(subject: subj, predicate: type, object: vtype) 294 | let t2 = TriplePattern(subject: subj, predicate: name, object: vname) 295 | 296 | let algebra : Algebra = .filter(.bgp([t1]), .exists(.bgp([t2]))) 297 | 298 | var bgpCount = 0 299 | var variables = Set() 300 | let recursiveType = WalkType(descendIntoAlgebras: true, descendIntoSubqueries: true, descendIntoExpressions: true) 301 | 302 | let recursiveConfig = WalkConfig(type: recursiveType, algebraHandler: { (a) in 303 | switch a { 304 | case .bgp(_): 305 | bgpCount += 1 306 | variables.formUnion(a.inscope) 307 | default: 308 | break 309 | } 310 | }) 311 | try algebra.walk(config: recursiveConfig) 312 | 313 | XCTAssertEqual(bgpCount, 2) 314 | XCTAssertEqual(variables, ["type", "name"]) 315 | } 316 | 317 | func testWalkRecursive_AlgebraExtendAlgebra() throws { 318 | let subj: Node = .bound(Term(value: "b", type: .blank)) 319 | let type: Node = .bound(Term(value: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", type: .iri)) 320 | let name: Node = .bound(Term(value: "http://xmlns.com/foaf/0.1/name", type: .iri)) 321 | let vtype: Node = .variable("type", binding: true) 322 | let vname: Node = .variable("name", binding: true) 323 | let t1 = TriplePattern(subject: subj, predicate: type, object: vtype) 324 | let t2 = TriplePattern(subject: subj, predicate: name, object: vname) 325 | 326 | let algebra : Algebra = .extend(.bgp([t1]), .exists(.bgp([t2])), "e") 327 | 328 | var bgpCount = 0 329 | var variables = Set() 330 | let recursiveType = WalkType(descendIntoAlgebras: true, descendIntoSubqueries: true, descendIntoExpressions: true) 331 | 332 | let recursiveConfig = WalkConfig(type: recursiveType, algebraHandler: { (a) in 333 | switch a { 334 | case .bgp(_): 335 | bgpCount += 1 336 | variables.formUnion(a.inscope) 337 | default: 338 | break 339 | } 340 | }) 341 | try algebra.walk(config: recursiveConfig) 342 | 343 | XCTAssertEqual(bgpCount, 2) 344 | XCTAssertEqual(variables, ["type", "name"]) 345 | } 346 | 347 | func testWalkRecursive_AlgebraAggregationAlgebra() throws { 348 | let sparql = """ 349 | SELECT (COUNT(EXISTS { ?s

1 OPTIONAL { ?s 2 } }) AS ?count) { 350 | ?s ?p ?o 351 | } 352 | 353 | """ 354 | guard var p = SPARQLParser(string: sparql) else { XCTFail(); return } 355 | let q = try p.parseQuery() 356 | let algebra = q.algebra 357 | 358 | var leftJoinCount = 0 359 | let recursiveType = WalkType(descendIntoAlgebras: true, descendIntoSubqueries: true, descendIntoExpressions: true) 360 | 361 | let recursiveConfig = WalkConfig(type: recursiveType, algebraHandler: { (a) in 362 | switch a { 363 | case .leftOuterJoin: 364 | leftJoinCount += 1 365 | default: 366 | break 367 | } 368 | }) 369 | try algebra.walk(config: recursiveConfig) 370 | 371 | XCTAssertEqual(leftJoinCount, 1) 372 | } 373 | 374 | func testWalkRecursive_AlgebraWindowAlgebra() throws { 375 | let sparql = """ 376 | SELECT (COUNT(EXISTS { ?s

1 OPTIONAL { ?s 2 } }) OVER (PARTITION BY ?s ?o ORDER BY ?o RANGE BETWEEN 3 PRECEDING AND current row) AS ?windowCount) 377 | WHERE { ?s ?p ?o } 378 | 379 | """ 380 | guard var p = SPARQLParser(string: sparql) else { XCTFail(); return } 381 | let q = try p.parseQuery() 382 | let algebra = q.algebra 383 | 384 | var leftJoinCount = 0 385 | let recursiveType = WalkType(descendIntoAlgebras: true, descendIntoSubqueries: true, descendIntoExpressions: true) 386 | 387 | let recursiveConfig = WalkConfig(type: recursiveType, algebraHandler: { (a) in 388 | switch a { 389 | case .leftOuterJoin: 390 | leftJoinCount += 1 391 | default: 392 | break 393 | } 394 | }) 395 | try algebra.walk(config: recursiveConfig) 396 | 397 | XCTAssertEqual(leftJoinCount, 1) 398 | } 399 | 400 | func testBind() throws { 401 | let subj: Node = .bound(Term(value: "b", type: .blank)) 402 | let type: Node = .bound(Term(iri: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type")) 403 | let person: Node = .bound(Term(iri: "http://xmlns.com/foaf/0.1/Person")) 404 | let name: Node = .bound(Term(iri: "http://xmlns.com/foaf/0.1/name")) 405 | let vtype: Node = .variable("type", binding: true) 406 | let vname: Node = .variable("name", binding: true) 407 | let t1 = TriplePattern(subject: subj, predicate: type, object: vtype) 408 | let t1bound = TriplePattern(subject: subj, predicate: type, object: person) 409 | let t2 = TriplePattern(subject: subj, predicate: name, object: vname) 410 | 411 | let algebra = Algebra.order( 412 | .project( 413 | .filter( 414 | .distinct( 415 | .innerJoin( 416 | .union( 417 | .unionIdentity, 418 | .leftOuterJoin( 419 | .bgp([t1]), 420 | .triple(t2), 421 | .node(Node(term: Term.trueValue)) 422 | ) 423 | ), 424 | .joinIdentity 425 | ) 426 | ), 427 | .node(Node(term: Term.trueValue)) 428 | ), 429 | Set(["type", "name"]) 430 | ), 431 | [Algebra.SortComparator(ascending: true, expression: Expression(variable: "name"))] 432 | ) 433 | 434 | XCTAssertEqual(algebra.inscope, Set(["name", "type"])) 435 | let r = try algebra.bind("type", to: Node(term: Term(iri: "http://xmlns.com/foaf/0.1/Person")), preservingProjection: true) 436 | let expected = Algebra.order( 437 | .project( 438 | .extend( 439 | .filter( 440 | .distinct( 441 | .innerJoin( 442 | .union( 443 | .unionIdentity, 444 | .leftOuterJoin( 445 | .bgp([t1bound]), 446 | .triple(t2), 447 | .node(Node(term: Term.trueValue)) 448 | ) 449 | ), 450 | .joinIdentity 451 | ) 452 | ), 453 | .node(Node(term: Term.trueValue)) 454 | ), 455 | .node(person), 456 | "type" 457 | ), 458 | Set(["type", "name"]) 459 | ), 460 | [Algebra.SortComparator(ascending: true, expression: Expression(variable: "name"))] 461 | ) 462 | XCTAssertEqual(r, expected) 463 | } 464 | 465 | func testReplacementMap() throws { 466 | let subj: Node = .bound(Term(value: "b", type: .blank)) 467 | let type: Node = .bound(Term(iri: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type")) 468 | let name: Node = .bound(Term(iri: "http://xmlns.com/foaf/0.1/name")) 469 | let eve: Node = .bound(Term(string: "Eve")) 470 | let vtype: Node = .variable("type", binding: true) 471 | let vname: Node = .variable("name", binding: true) 472 | let t1 = TriplePattern(subject: subj, predicate: type, object: vtype) 473 | let t2 = TriplePattern(subject: subj, predicate: name, object: vname) 474 | let t2replaced = TriplePattern(subject: subj, predicate: name, object: eve) 475 | 476 | let algebra = Algebra.order( 477 | .project( 478 | .filter( 479 | .distinct( 480 | .innerJoin( 481 | .union( 482 | .unionIdentity, 483 | .leftOuterJoin( 484 | .bgp([t1]), 485 | .triple(t2), 486 | .node(Node(term: Term.trueValue)) 487 | ) 488 | ), 489 | .joinIdentity 490 | ) 491 | ), 492 | .node(Node(term: Term.trueValue)) 493 | ), 494 | Set(["type", "name"]) 495 | ), 496 | [Algebra.SortComparator(ascending: true, expression: Expression(variable: "name"))] 497 | ) 498 | 499 | let r = try algebra.replace(["name": Term(string: "Eve")]) 500 | let expected = Algebra.order( 501 | .project( 502 | .filter( 503 | .distinct( 504 | .innerJoin( 505 | .union( 506 | .unionIdentity, 507 | .leftOuterJoin( 508 | .bgp([t1]), 509 | .triple(t2replaced), 510 | .node(Node(term: Term.trueValue)) 511 | ) 512 | ), 513 | .joinIdentity 514 | ) 515 | ), 516 | .node(Node(term: Term.trueValue)) 517 | ), 518 | Set(["type", "name"]) 519 | ), 520 | [Algebra.SortComparator(ascending: true, expression: .node(eve))] 521 | ) 522 | XCTAssertEqual(r, expected) 523 | } 524 | 525 | func testReplacementFunc() throws { 526 | let subj: Node = .bound(Term(value: "b", type: .blank)) 527 | let type: Node = .bound(Term(iri: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type")) 528 | let name: Node = .bound(Term(iri: "http://xmlns.com/foaf/0.1/name")) 529 | let vtype: Node = .variable("type", binding: true) 530 | let vname: Node = .variable("name", binding: true) 531 | let t1 = TriplePattern(subject: subj, predicate: type, object: vtype) 532 | let t2 = TriplePattern(subject: subj, predicate: name, object: vname) 533 | 534 | let algebra = Algebra.order( 535 | .project( 536 | .filter( 537 | .distinct( 538 | .innerJoin( 539 | .union( 540 | .unionIdentity, 541 | .leftOuterJoin( 542 | .bgp([t1]), 543 | .triple(t2), 544 | .node(Node(term: Term.trueValue)) 545 | ) 546 | ), 547 | .joinIdentity 548 | ) 549 | ), 550 | .node(Node(term: Term.trueValue)) 551 | ), 552 | Set(["type", "name"]) 553 | ), 554 | [Algebra.SortComparator(ascending: true, expression: Expression(variable: "name"))] 555 | ) 556 | 557 | let r = try algebra.replace { (a) -> Algebra? in 558 | switch a { 559 | case .triple(let t): 560 | return .bgp([t]) 561 | default: 562 | return nil 563 | } 564 | } 565 | let expected = Algebra.order( 566 | .project( 567 | .filter( 568 | .distinct( 569 | .innerJoin( 570 | .union( 571 | .unionIdentity, 572 | .leftOuterJoin( 573 | .bgp([t1]), 574 | .bgp([t2]), 575 | .node(Node(term: Term.trueValue)) 576 | ) 577 | ), 578 | .joinIdentity 579 | ) 580 | ), 581 | .node(Node(term: Term.trueValue)) 582 | ), 583 | Set(["type", "name"]) 584 | ), 585 | [Algebra.SortComparator(ascending: true, expression: Expression(variable: "name"))] 586 | ) 587 | XCTAssertEqual(r, expected) 588 | } 589 | 590 | } 591 | -------------------------------------------------------------------------------- /Tests/SPARQLSyntaxTests/ExpressionTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Foundation 3 | @testable import SPARQLSyntax 4 | 5 | #if os(Linux) 6 | extension ExpressionTest { 7 | static var allTests : [(String, (ExpressionTest) -> () throws -> Void)] { 8 | return [ 9 | ("testRemoveAggregation", testRemoveAggregation), 10 | ] 11 | } 12 | } 13 | #endif 14 | 15 | // swiftlint:disable type_body_length 16 | class ExpressionTest: XCTestCase { 17 | 18 | override func setUp() { 19 | super.setUp() 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | override func tearDown() { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | super.tearDown() 26 | } 27 | 28 | func wrapDeepExpression(_ expr : SPARQLSyntax.Expression) -> SPARQLSyntax.Expression { 29 | let v1 = Expression(variable: "v1") 30 | let v2 = Expression.node(.variable("v2", binding: false)) 31 | 32 | let l1 = Expression.node(.bound(Term(string: "en-US"))) 33 | 34 | let i1 = Expression(integer: 1) 35 | let i2 = Expression.node(.bound(Term(integer: 2))) 36 | let i3 = Expression.node(.bound(Term(integer: 3))) 37 | 38 | let call1 = Expression.call("tag:kasei.us,2018:example-function", []) 39 | 40 | var e = expr 41 | 42 | e = .div(.sub(.mul(i2, .add(.neg(expr), i1)), i3), i1) 43 | e = .between(e, i1, i2) 44 | e = .not(e) 45 | e = .and(e, .bound(v2)) 46 | e = .or(e, .lt(v1, i2)) 47 | e = .and(e, .isiri(v1)) 48 | e = .and(e, .isblank(v1)) 49 | e = .and(e, .isliteral(v1)) 50 | e = .and(e, .isnumeric(v1)) 51 | e = .and(e, .langmatches(.lang(.stringCast(v2)), l1)) 52 | e = .and(e, .boolCast(call1)) 53 | e = .and(e, .eq(v1, .intCast(v2))) 54 | e = .and(e, .ne(v1, .floatCast(v2))) 55 | e = .and(e, .lt(v1, .doubleCast(v2))) 56 | e = .and(e, .le(v1, .decimalCast(v2))) 57 | e = .and(e, .gt(v1, v2)) 58 | e = .and(e, .ge(v1, v2)) 59 | e = .and(e, .sameterm(.datatype(v1), v2)) 60 | e = .and(e, .valuein(v2, [i1, i2])) 61 | 62 | /** 63 | case dateTimeCast(Expression) 64 | case dateCast(Expression) 65 | case valuein(Expression, [Expression]) 66 | case exists(Algebra) 67 | 68 | **/ 69 | return e 70 | } 71 | 72 | func testBuiltInExpression() { 73 | let v1 = Expression.node(.variable("v1", binding: false)) 74 | XCTAssertTrue(Expression.isiri(v1).isBuiltInCall) 75 | XCTAssertFalse(Expression.call("http://example.org/test-function", [v1]).isBuiltInCall) 76 | } 77 | 78 | func testIsNumeric() { 79 | let v1 = Expression(variable: "v1") 80 | let i1 = Expression(integer: 1) 81 | let i2 = Expression(integer: 2) 82 | XCTAssertFalse(Expression.isiri(v1).isNumeric) 83 | XCTAssertFalse(Expression.add(v1, i1).isNumeric) 84 | XCTAssertTrue(Expression.add(.intCast(i2), i1).isNumeric) 85 | } 86 | 87 | func testRemoveAggregation() { 88 | let freshCounter = AnyIterator(sequence(first: 1) { $0 + 1 }) 89 | let agg : Aggregation = .sum(.node(.variable("var", binding: true)), false) 90 | let expr = wrapDeepExpression(.aggregate(agg)) 91 | XCTAssertEqual(expr.variables, Set(["v1", "v2", "var"])) 92 | XCTAssertTrue(expr.hasAggregation) 93 | let expected = wrapDeepExpression(.node(.variable(".agg-1", binding: true))) 94 | var mapping = [String:Aggregation]() 95 | let r = expr.removeAggregations(freshCounter, mapping: &mapping) 96 | XCTAssertFalse(r.hasAggregation) 97 | XCTAssertEqual(r.variables, Set(["v1", "v2", ".agg-1"])) 98 | XCTAssertEqual(r, expected) 99 | XCTAssertEqual(mapping, [".agg-1": agg]) 100 | } 101 | 102 | func testReplacementDeepMap() throws { 103 | let agg : Aggregation = .sum(.node(.variable("var", binding: true)), false) 104 | let expr = wrapDeepExpression(.aggregate(agg)) 105 | XCTAssertEqual(expr.description, """ 106 | ((((((((((((((((NOT(((((2 * (-(SUM(?var)) + 1)) - 3) / 1) BETWEEN 1 AND 2)) && BOUND(?v2)) || (?v1 < 2)) && ISIRI(?v1)) && ISBLANK(?v1)) && ISLITERAL(?v1)) && ISNUMERIC(?v1)) && LANGMATCHES(LANG(xsd:string(?v2)), ""en-US"")) && xsd:boolean(())) && (?v1 == xsd:integer(?v2))) && (?v1 != xsd:float(?v2))) && (?v1 < xsd:double(?v2))) && (?v1 <= xsd:decimal(?v2))) && (?v1 > ?v2)) && (?v1 >= ?v2)) && SAMETERM(DATATYPE(?v1), ?v2)) && ?v2 IN (1,2)) 107 | """) 108 | let r = try expr.replace(["var": Term(integer: 2)]) 109 | XCTAssertEqual(r.description, """ 110 | ((((((((((((((((NOT(((((2 * (-(SUM(2)) + 1)) - 3) / 1) BETWEEN 1 AND 2)) && BOUND(?v2)) || (?v1 < 2)) && ISIRI(?v1)) && ISBLANK(?v1)) && ISLITERAL(?v1)) && ISNUMERIC(?v1)) && LANGMATCHES(LANG(xsd:string(?v2)), ""en-US"")) && xsd:boolean(())) && (?v1 == xsd:integer(?v2))) && (?v1 != xsd:float(?v2))) && (?v1 < xsd:double(?v2))) && (?v1 <= xsd:decimal(?v2))) && (?v1 > ?v2)) && (?v1 >= ?v2)) && SAMETERM(DATATYPE(?v1), ?v2)) && ?v2 IN (1,2)) 111 | """) 112 | } 113 | 114 | func testReplacementMap() throws { 115 | let agg : Aggregation = .sum(.node(.variable("var", binding: true)), false) 116 | let expr = Expression.add(Expression(integer: 2), .aggregate(agg)) 117 | XCTAssertEqual(expr.description, "(2 + SUM(?var))") 118 | let r = try expr.replace(["var": Term(integer: 2)]) 119 | XCTAssertEqual(r.description, "(2 + SUM(2))") 120 | } 121 | 122 | func testReplacementFunc() throws { 123 | let agg : Aggregation = .sum(.node(.variable("var", binding: true)), false) 124 | let expr = wrapDeepExpression(.aggregate(agg)) 125 | let r = try expr.replace { (e) -> SPARQLSyntax.Expression? in 126 | switch e { 127 | case .and(_, _): 128 | return Expression(variable: "xxx") 129 | default: 130 | return e 131 | } 132 | } 133 | XCTAssertEqual(r, Expression(variable: "xxx")) 134 | } 135 | 136 | func testEncodable() throws { 137 | let agg : Aggregation = .sum(.node(.variable("var", binding: true)), false) 138 | let expr = wrapDeepExpression(.aggregate(agg)) 139 | let je = JSONEncoder() 140 | let jd = JSONDecoder() 141 | let data = try je.encode(expr) 142 | let r = try jd.decode(Expression.self, from: data) 143 | XCTAssertEqual(r.description, """ 144 | ((((((((((((((((NOT(((((2 * (-(SUM(?var)) + 1)) - 3) / 1) BETWEEN 1 AND 2)) && BOUND(?v2)) || (?v1 < 2)) && ISIRI(?v1)) && ISBLANK(?v1)) && ISLITERAL(?v1)) && ISNUMERIC(?v1)) && LANGMATCHES(LANG(xsd:string(?v2)), ""en-US"")) && xsd:boolean(())) && (?v1 == xsd:integer(?v2))) && (?v1 != xsd:float(?v2))) && (?v1 < xsd:double(?v2))) && (?v1 <= xsd:decimal(?v2))) && (?v1 > ?v2)) && (?v1 >= ?v2)) && SAMETERM(DATATYPE(?v1), ?v2)) && ?v2 IN (1,2)) 145 | """) 146 | } 147 | 148 | func testWalkRecursive_ExistsAlgebraFilter() throws { 149 | let subj: Node = .bound(Term(value: "b", type: .blank)) 150 | let type: Node = .bound(Term(value: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", type: .iri)) 151 | let vtype: Node = .variable("type", binding: true) 152 | let vname: Node = .variable("name", binding: true) 153 | let t1 = TriplePattern(subject: subj, predicate: type, object: vtype) 154 | 155 | let expr : SPARQLSyntax.Expression = .exists(.filter(.bgp([t1]), .eq(.node(vtype), .node(vname)))) 156 | 157 | var variables = Set() 158 | let recursiveType = WalkType(descendIntoAlgebras: true, descendIntoSubqueries: true, descendIntoExpressions: true) 159 | 160 | let recursiveConfig = WalkConfig(type: recursiveType, expressionHandler: { (e) in 161 | switch e { 162 | case .node(.variable(let v, binding: _)): 163 | variables.insert(v) 164 | default: 165 | break 166 | } 167 | }) 168 | try expr.walk(config: recursiveConfig) 169 | 170 | XCTAssertEqual(variables, ["type", "name"]) 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /Tests/SPARQLSyntaxTests/IRITests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Foundation 3 | import SPARQLSyntax 4 | 5 | #if os(Linux) 6 | extension IRITest { 7 | static var allTests : [(String, (IRITest) -> () throws -> Void)] { 8 | return [ 9 | ("testIRI_AbsoluteWithBase", testIRI_AbsoluteWithBase), 10 | ("testIRI_FragmentWithBase", testIRI_FragmentWithBase), 11 | ("testIRI_FullPathWithBase", testIRI_FullPathWithBase), 12 | ("testIRI_RelativeWithBase", testIRI_RelativeWithBase), 13 | ("testIRI_Namespace", testIRI_Namespace), 14 | ] 15 | } 16 | } 17 | #endif 18 | 19 | // swiftlint:disable type_body_length 20 | class IRITest: XCTestCase { 21 | 22 | override func setUp() { 23 | super.setUp() 24 | // Put setup code here. This method is called before the invocation of each test method in the class. 25 | } 26 | 27 | override func tearDown() { 28 | // Put teardown code here. This method is called after the invocation of each test method in the class. 29 | super.tearDown() 30 | } 31 | 32 | func testIRI_FragmentWithBase() { 33 | let base = IRI(string: "file:///Users/greg/data/prog/git/sparql/kineo/rdf-tests/sparql11/data-r2/algebra/two-nested-opt.rq") 34 | XCTAssertNotNil(base) 35 | let rel = "#x1" 36 | let i = IRI(string: rel, relativeTo: base) 37 | XCTAssertNotNil(i) 38 | if let i = i { 39 | XCTAssertEqual(i.absoluteString, "file:///Users/greg/data/prog/git/sparql/kineo/rdf-tests/sparql11/data-r2/algebra/two-nested-opt.rq#x1") 40 | } 41 | } 42 | 43 | func testIRI_RelativeWithBase() { 44 | let base = IRI(string: "file:///Users/greg/data/prog/git/sparql/kineo/rdf-tests/sparql11/data-r2/algebra/two-nested-opt.rq") 45 | let rel = "x1" 46 | let i = IRI(string: rel, relativeTo: base) 47 | XCTAssertNotNil(i) 48 | XCTAssertEqual(i!.absoluteString, "file:///Users/greg/data/prog/git/sparql/kineo/rdf-tests/sparql11/data-r2/algebra/x1") 49 | } 50 | 51 | func testIRI_FullPathWithBase() { 52 | let base = IRI(string: "file:///Users/greg/data/prog/git/sparql/kineo/rdf-tests/sparql11/data-r2/algebra/two-nested-opt.rq") 53 | let rel = "/x1" 54 | let i = IRI(string: rel, relativeTo: base) 55 | XCTAssertNotNil(i) 56 | XCTAssertEqual(i!.absoluteString, "file:///x1") 57 | } 58 | 59 | func testIRI_Namespace() { 60 | let ns = Namespace.rdf 61 | let type = ns.type 62 | XCTAssertEqual(type, "http://www.w3.org/1999/02/22-rdf-syntax-ns#type") 63 | 64 | guard let n = ns.iri(for: "nil") else { 65 | XCTFail() 66 | return 67 | } 68 | 69 | XCTAssertEqual(n.absoluteString, "http://www.w3.org/1999/02/22-rdf-syntax-ns#nil") 70 | } 71 | 72 | func testIRI_file() { 73 | let i = IRI(fileURLWithPath: "/tmp") 74 | XCTAssertNotNil(i) 75 | let iri = i! 76 | XCTAssertEqual(iri.absoluteString, "file:///tmp") 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Tests/SPARQLSyntaxTests/PropertyPathTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Foundation 3 | @testable import SPARQLSyntax 4 | 5 | #if os(Linux) 6 | extension PropertyPathTest { 7 | static var allTests : [(String, (PropertyPathTest) -> () throws -> Void)] { 8 | return [ 9 | ("testComparable", testComparable), 10 | ("testEncodable", testEncodable), 11 | ("testDescription", testDescription), 12 | ] 13 | } 14 | } 15 | #endif 16 | 17 | // swiftlint:disable type_body_length 18 | class PropertyPathTest: XCTestCase { 19 | 20 | override func setUp() { 21 | super.setUp() 22 | // Put setup code here. This method is called before the invocation of each test method in the class. 23 | } 24 | 25 | override func tearDown() { 26 | // Put teardown code here. This method is called after the invocation of each test method in the class. 27 | super.tearDown() 28 | } 29 | 30 | func deepPath() -> PropertyPath { 31 | let p1 = Term(iri: "http://example.org/p1") 32 | let p2 = Term(iri: "http://example.org/p2") 33 | let p3 = Term(iri: "http://example.org/p3") 34 | 35 | let pp : PropertyPath = .seq( 36 | .seq( 37 | .alt(.link(p1), .plus(.link(p2))), 38 | .star(.inv(.link(p3))) 39 | ), 40 | .zeroOrOne(.nps([p1, p2])) 41 | ) 42 | return pp 43 | } 44 | 45 | func testComparable() throws { 46 | let pp = deepPath() 47 | let p1 = Term(iri: "http://example.org/abc") 48 | let p2 = Term(iri: "http://example.org/xyz") 49 | XCTAssertTrue(pp >= pp) 50 | XCTAssertFalse(pp < pp) 51 | 52 | XCTAssertLessThan(PropertyPath.link(p1), PropertyPath.link(p2)) 53 | XCTAssertGreaterThan(PropertyPath.link(p2), PropertyPath.link(p1)) 54 | 55 | XCTAssertLessThan(pp, PropertyPath.link(p1)) 56 | XCTAssertLessThan(pp, PropertyPath.seq(.link(p1), .link(p1))) 57 | XCTAssertLessThan(pp, PropertyPath.inv(.link(p1))) 58 | XCTAssertLessThan(pp, PropertyPath.nps([p1])) 59 | XCTAssertLessThan(pp, PropertyPath.star(.link(p1))) 60 | XCTAssertLessThan(pp, PropertyPath.plus(.link(p1))) 61 | XCTAssertLessThan(pp, PropertyPath.zeroOrOne(.link(p1))) 62 | } 63 | 64 | func testDescription() throws { 65 | let pp = deepPath() 66 | XCTAssertEqual(pp.description, """ 67 | seq(seq(alt(, oneOrMore()), zeroOrMore(inv())), zeroOrOne(NPS([, ]))) 68 | """) 69 | } 70 | 71 | func testEncodable() throws { 72 | let pp = deepPath() 73 | let je = JSONEncoder() 74 | let jd = JSONDecoder() 75 | let data = try je.encode(pp) 76 | let r = try jd.decode(PropertyPath.self, from: data) 77 | XCTAssertEqual(r, pp) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Tests/SPARQLSyntaxTests/RDFPatternsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Foundation 3 | import SPARQLSyntax 4 | 5 | #if os(Linux) 6 | extension RDFPatternsTest { 7 | static var allTests : [(String, (RDFPatternsTest) -> () throws -> Void)] { 8 | return [ 9 | ("test_predicateSet", test_predicateSet), 10 | ] 11 | } 12 | } 13 | #endif 14 | 15 | // swiftlint:disable type_body_length 16 | class RDFPatternsTest: XCTestCase { 17 | 18 | override func setUp() { 19 | super.setUp() 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | override func tearDown() { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | super.tearDown() 26 | } 27 | 28 | func test_predicateSet() { 29 | let ex = TermNamespace(namespace: Namespace(value: "http://example.org/")) 30 | let nex = NodeNamespace(namespace: Namespace(value: "http://example.org/")) 31 | 32 | let rdf = TermNamespace(namespace: Namespace.rdf) 33 | let nrdf = NodeNamespace(namespace: Namespace.rdf) 34 | 35 | let patterns : [TriplePattern] = [ 36 | TriplePattern(subject: nex.s3b, predicate: nrdf.type, object: nex.Type1), 37 | TriplePattern(subject: nex.s3b, predicate: nex.p2, object: .bound(Term.trueValue)), 38 | TriplePattern(subject: nex.s3b, predicate: nex.p2, object: .bound(Term.falseValue)), 39 | TriplePattern(subject: nex.s3b, predicate: nex.p3, object: .bound(Term.trueValue)), 40 | ] 41 | 42 | let bgp = BGP(patterns) 43 | let preds = bgp.predicateSet 44 | XCTAssertEqual(preds, Set([rdf.type, ex.p2, ex.p3])) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Tests/SPARQLSyntaxTests/RDFTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Foundation 3 | @testable import SPARQLSyntax 4 | 5 | #if os(Linux) 6 | extension RDFTest { 7 | static var allTests : [(String, (RDFTest) -> () throws -> Void)] { 8 | return [ 9 | ("testConstructorDecimal", testConstructorDecimal), 10 | ("testConstructorDecimal2", testConstructorDecimal2), 11 | ("testConstructorDouble", testConstructorDouble), 12 | ("testConstructorDouble2", testConstructorDouble2), 13 | ("testConstructorDouble3", testConstructorDouble3), 14 | ("testConstructorFloat", testConstructorFloat), 15 | ("testConstructorFloat2", testConstructorFloat2), 16 | ("testConstructorInteger", testConstructorInteger), 17 | ("testDateTerms1", testDateTerms1), 18 | ("testDecimalRound1", testDecimalRound1), 19 | ("testDecimalRound2", testDecimalRound2), 20 | ("testQuadPattern_bindings", testQuadPattern_bindings), 21 | ("testQuadPattern_matches", testQuadPattern_matches), 22 | ("testQuadPattern_matches_shared_var", testQuadPattern_matches_shared_var), 23 | ("testQuadPattern_matches_shared_var_nonbinding", testQuadPattern_matches_shared_var_nonbinding), 24 | ("testTermJSON_Blank", testTermJSON_Blank), 25 | ("testTermJSON_IRI", testTermJSON_IRI), 26 | ("testTermJSON_LanguageLiteral", testTermJSON_LanguageLiteral), 27 | ("testTermJSON_SimpleLiteral", testTermJSON_SimpleLiteral), 28 | ] 29 | } 30 | } 31 | #endif 32 | 33 | enum TestError: Error { 34 | case error 35 | } 36 | 37 | // swiftlint:disable type_body_length 38 | class RDFTest: XCTestCase { 39 | 40 | override func setUp() { 41 | super.setUp() 42 | // Put setup code here. This method is called before the invocation of each test method in the class. 43 | } 44 | 45 | override func tearDown() { 46 | // Put teardown code here. This method is called after the invocation of each test method in the class. 47 | super.tearDown() 48 | } 49 | 50 | func testConstructorInteger() { 51 | let t = Term(integer: 7) 52 | XCTAssertEqual(t.value, "7") 53 | } 54 | 55 | func testConstructorDecimal() { 56 | let t = Term(decimal: 7.1) 57 | XCTAssertEqual(t.value, "7.1") 58 | } 59 | 60 | func testDateTerms1() throws { 61 | let date = Date(timeIntervalSince1970: 1559577112) 62 | let term1 = Term(dateTime: date, timeZone: nil) 63 | XCTAssertEqual(term1.value, "2019-06-03T15:51:52") 64 | 65 | guard let tz2 = TimeZone(secondsFromGMT: 3600) else { 66 | throw TestError.error 67 | } 68 | 69 | let term2 = Term(dateTime: date, timeZone: tz2) 70 | XCTAssertEqual(term2.value, "2019-06-03T15:51:52+01:00") 71 | 72 | guard let tz3 = TimeZone(secondsFromGMT: -36_000) else { 73 | throw TestError.error 74 | } 75 | 76 | let term3 = Term(dateTime: date, timeZone: tz3) 77 | XCTAssertEqual(term3.value, "2019-06-03T15:51:52-10:00") 78 | } 79 | 80 | func testDecimalRound1() { 81 | let t = Term(decimal: 7.1) 82 | let r = t.numeric!.round 83 | guard case .decimal(let decimalValue) = r else { 84 | XCTFail() 85 | return 86 | } 87 | XCTAssertEqual(decimalValue, 7.0) 88 | } 89 | 90 | func testDecimalRound2() { 91 | let decimal = Decimal(sign: .minus, exponent: -1, significand: 75) // -7.5 92 | let t = Term(decimal: decimal) 93 | let r = t.numeric!.round 94 | guard case .decimal(let decimalValue) = r else { 95 | XCTFail() 96 | return 97 | } 98 | XCTAssertEqual(decimalValue, -8.0) 99 | } 100 | 101 | func testConstructorDecimal2() { 102 | let t = Term(value: "-017.10", type: .datatype(.decimal)) 103 | XCTAssertEqual(t.value, "-17.1") 104 | } 105 | 106 | func testConstructorFloat() { 107 | let t = Term(float: -70.1) 108 | XCTAssertEqual(t.value, "-7.01E1") 109 | } 110 | 111 | func testConstructorFloat2() { 112 | let t = Term(float: -0.701, exponent: 1) 113 | XCTAssertEqual(t.value, "-7.01E0") 114 | } 115 | 116 | func testConstructorDouble() { 117 | let t = Term(double: 700.1) 118 | XCTAssertEqual(t.value, "7.001E2") 119 | } 120 | 121 | func testConstructorDouble2() { 122 | let t = Term(double: 7001.0, exponent: -1) 123 | XCTAssertEqual(t.value, "7.001E2") 124 | } 125 | 126 | func testConstructorDouble3() { 127 | let t = Term(double: 0.00123) 128 | XCTAssertEqual(t.value, "1.23E-3") 129 | } 130 | 131 | func testConstructorDouble4() { 132 | let t = Term(value: "Infinity", type: .datatype(.double)) 133 | XCTAssertEqual(t.value, "INF") 134 | } 135 | 136 | func testTermJSON_SimpleLiteral() throws { 137 | let t = Term(string: "foobar") 138 | let e = JSONEncoder() 139 | e.outputFormatting = .sortedKeys 140 | let j = try e.encode(t) 141 | let s = String(data: j, encoding: .utf8)! 142 | let expected = """ 143 | {"datatype":"http:\\/\\/www.w3.org\\/2001\\/XMLSchema#string","type":"literal","value":"foobar"} 144 | """ 145 | XCTAssertEqual(s, expected) 146 | } 147 | 148 | func testTermJSON_LanguageLiteral() throws { 149 | let t = Term(value: "foobar", type: .language("en-us")) 150 | let e = JSONEncoder() 151 | e.outputFormatting = .sortedKeys 152 | let j = try e.encode(t) 153 | let s = String(data: j, encoding: .utf8)! 154 | let expected = """ 155 | {"type":"literal","value":"foobar","xml:lang":"en-us"} 156 | """ 157 | XCTAssertEqual(s, expected) 158 | } 159 | 160 | func testTermJSON_Blank() throws { 161 | let t = Term(value: "b1", type: .blank) 162 | let e = JSONEncoder() 163 | e.outputFormatting = .sortedKeys 164 | let j = try e.encode(t) 165 | let s = String(data: j, encoding: .utf8)! 166 | let expected = """ 167 | {"type":"bnode","value":"b1"} 168 | """ 169 | XCTAssertEqual(s, expected) 170 | } 171 | 172 | func testTermJSON_IRI() throws { 173 | let t = Term(iri: "https://www.w3.org/TR/sparql11-results-json/#select-encode-terms") 174 | let e = JSONEncoder() 175 | e.outputFormatting = .sortedKeys 176 | let j = try e.encode(t) 177 | let s = String(data: j, encoding: .utf8)! 178 | let expected = """ 179 | {"type":"uri","value":"https:\\/\\/www.w3.org\\/TR\\/sparql11-results-json\\/#select-encode-terms"} 180 | """ 181 | XCTAssertEqual(s, expected) 182 | } 183 | 184 | func testQuadPattern_matches() throws { 185 | let q = Quad(subject: Term(iri: "http://example.org/s"), predicate: Term.rdf("type"), object: Term(iri: "http://xmlns.com/foaf/0.1/Person"), graph: Term(iri: "http://example.org/data")) 186 | let qp = QuadPattern( 187 | subject: .variable("s", binding: true), 188 | predicate: .bound(Term.rdf("type")), 189 | object: .variable("class", binding: true), 190 | graph: .variable("graph", binding: true) 191 | ) 192 | XCTAssertTrue(qp.matches(q)) 193 | } 194 | 195 | func testQuadPattern_matches_shared_var() throws { 196 | let q1 = Quad(subject: Term(iri: "http://example.org/s"), predicate: Term.rdf("type"), object: Term(iri: "http://xmlns.com/foaf/0.1/Person"), graph: Term(iri: "http://example.org/s")) 197 | let q2 = Quad(subject: Term(iri: "http://example.org/s"), predicate: Term.rdf("type"), object: Term(iri: "http://xmlns.com/foaf/0.1/Person"), graph: Term(iri: "http://example.org/data")) 198 | let qp = QuadPattern( 199 | subject: .variable("s", binding: true), 200 | predicate: .bound(Term.rdf("type")), 201 | object: .variable("class", binding: true), 202 | graph: .variable("s", binding: true) 203 | ) 204 | XCTAssertTrue(qp.matches(q1)) 205 | XCTAssertFalse(qp.matches(q2)) 206 | } 207 | 208 | func testQuadPattern_matches_shared_var_nonbinding() throws { 209 | let q2 = Quad(subject: Term(iri: "http://example.org/s"), predicate: Term.rdf("type"), object: Term(iri: "http://xmlns.com/foaf/0.1/Person"), graph: Term(iri: "http://example.org/data")) 210 | let qp = QuadPattern( 211 | subject: .variable("s", binding: true), 212 | predicate: .bound(Term.rdf("type")), 213 | object: .variable("class", binding: true), 214 | graph: .variable("s", binding: false) 215 | ) 216 | XCTAssertTrue(qp.matches(q2)) 217 | } 218 | 219 | func testQuadPattern_bindings() throws { 220 | let q2 = Quad(subject: Term(iri: "http://example.org/s"), predicate: Term.rdf("type"), object: Term(iri: "http://xmlns.com/foaf/0.1/Person"), graph: Term(iri: "http://example.org/data")) 221 | let qp = QuadPattern( 222 | subject: .variable("s", binding: true), 223 | predicate: .bound(Term.rdf("type")), 224 | object: .variable("class", binding: true), 225 | graph: .variable("s", binding: false) 226 | ) 227 | let b = qp.bindings(for: q2) 228 | XCTAssertNotNil(b) 229 | XCTAssertEqual(b, .some([ 230 | "s": Term(iri: "http://example.org/s"), 231 | "class": Term(iri: "http://xmlns.com/foaf/0.1/Person") 232 | ])) 233 | } 234 | 235 | func testNumericCanonicalization() throws { 236 | // these values should canonicalize and have the same lexical form 237 | 238 | let double1 = Term(value: "1E3", type: .datatype(.double)) 239 | let double2 = Term(value: "100e1", type: .datatype(.double)) 240 | XCTAssertEqual(double1.value, double2.value) 241 | 242 | let double3 = Term(value: "123e-1", type: .datatype(.double)) 243 | XCTAssertEqual(double3.value, "1.23E1") 244 | 245 | let double4 = Term(value: "0e1", type: .datatype(.double)) 246 | XCTAssertEqual(double4.value, "0E0") 247 | 248 | let decimal1 = Term(value: "1.0", type: .datatype(.decimal)) 249 | let decimal2 = Term(value: "+1", type: .datatype(.decimal)) 250 | XCTAssertEqual(decimal1.value, decimal2.value) 251 | } 252 | 253 | func testLexicalForms() throws { 254 | // xsd:boolean 255 | XCTAssertTrue(Term.isValidLexicalForm("0", for: .boolean)) 256 | XCTAssertTrue(Term.isValidLexicalForm("1", for: .boolean)) 257 | XCTAssertTrue(Term.isValidLexicalForm("true", for: .boolean)) 258 | XCTAssertTrue(Term.isValidLexicalForm("false", for: .boolean)) 259 | 260 | // xsd:integer 261 | XCTAssertTrue(Term.isValidLexicalForm("-0", for: .integer)) 262 | XCTAssertTrue(Term.isValidLexicalForm("1", for: .integer)) 263 | XCTAssertTrue(Term.isValidLexicalForm("+1", for: .integer)) 264 | XCTAssertTrue(Term.isValidLexicalForm("-1", for: .integer)) 265 | XCTAssertTrue(Term.isValidLexicalForm("123", for: .integer)) 266 | XCTAssertTrue(Term.isValidLexicalForm("000", for: .integer)) 267 | 268 | // xsd:decimal 269 | XCTAssertTrue(Term.isValidLexicalForm("1", for: .decimal)) 270 | XCTAssertTrue(Term.isValidLexicalForm("+1.", for: .decimal)) 271 | XCTAssertTrue(Term.isValidLexicalForm("-1.0", for: .decimal)) 272 | XCTAssertTrue(Term.isValidLexicalForm("123.123", for: .decimal)) 273 | XCTAssertTrue(Term.isValidLexicalForm("000.000", for: .decimal)) 274 | XCTAssertTrue(Term.isValidLexicalForm(".123", for: .decimal)) 275 | XCTAssertTrue(Term.isValidLexicalForm(".0", for: .decimal)) 276 | XCTAssertTrue(Term.isValidLexicalForm(".00001", for: .decimal)) 277 | 278 | // xsd:double 279 | XCTAssertTrue(Term.isValidLexicalForm("1", for: .double)) 280 | XCTAssertTrue(Term.isValidLexicalForm("+1.", for: .double)) 281 | XCTAssertTrue(Term.isValidLexicalForm("-1.0", for: .double)) 282 | XCTAssertTrue(Term.isValidLexicalForm("123.123", for: .double)) 283 | XCTAssertTrue(Term.isValidLexicalForm("000.000", for: .double)) 284 | XCTAssertTrue(Term.isValidLexicalForm(".123", for: .double)) 285 | XCTAssertTrue(Term.isValidLexicalForm(".0", for: .double)) 286 | XCTAssertTrue(Term.isValidLexicalForm(".00001", for: .double)) 287 | XCTAssertTrue(Term.isValidLexicalForm("0.1E2", for: .double)) 288 | XCTAssertTrue(Term.isValidLexicalForm("0e-2", for: .double)) 289 | XCTAssertTrue(Term.isValidLexicalForm("123e+50", for: .double)) 290 | XCTAssertTrue(Term.isValidLexicalForm("-1.2e+5", for: .double)) 291 | } 292 | 293 | func testBadLexicalForms() throws { 294 | // xsd:boolean 295 | XCTAssertFalse(Term.isValidLexicalForm("00", for: .boolean)) 296 | XCTAssertFalse(Term.isValidLexicalForm("1000", for: .boolean)) 297 | XCTAssertFalse(Term.isValidLexicalForm("TRUE", for: .boolean)) 298 | XCTAssertFalse(Term.isValidLexicalForm("False", for: .boolean)) 299 | 300 | // xsd:integer 301 | XCTAssertFalse(Term.isValidLexicalForm("1.0", for: .integer)) 302 | XCTAssertFalse(Term.isValidLexicalForm(" -1", for: .integer)) 303 | XCTAssertFalse(Term.isValidLexicalForm("-+1", for: .integer)) 304 | XCTAssertFalse(Term.isValidLexicalForm("123.", for: .integer)) 305 | XCTAssertFalse(Term.isValidLexicalForm("000 ", for: .integer)) 306 | XCTAssertFalse(Term.isValidLexicalForm("", for: .integer)) 307 | 308 | // xsd:decimal 309 | XCTAssertFalse(Term.isValidLexicalForm("1e0", for: .decimal)) 310 | XCTAssertFalse(Term.isValidLexicalForm("1+", for: .decimal)) 311 | XCTAssertFalse(Term.isValidLexicalForm("-+1.0", for: .decimal)) 312 | XCTAssertFalse(Term.isValidLexicalForm(".", for: .decimal)) 313 | XCTAssertFalse(Term.isValidLexicalForm("", for: .decimal)) 314 | 315 | // xsd:double 316 | XCTAssertFalse(Term.isValidLexicalForm("1 ", for: .double)) 317 | XCTAssertFalse(Term.isValidLexicalForm("+1-", for: .double)) 318 | XCTAssertFalse(Term.isValidLexicalForm("-1E", for: .double)) 319 | XCTAssertFalse(Term.isValidLexicalForm("e0", for: .double)) 320 | XCTAssertFalse(Term.isValidLexicalForm(" ", for: .double)) 321 | XCTAssertFalse(Term.isValidLexicalForm("", for: .double)) 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /Tests/SPARQLSyntaxTests/SPARQLParserWindowTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Foundation 3 | import SPARQLSyntax 4 | 5 | #if os(Linux) 6 | extension SPARQLParserWindowTests { 7 | static var allTests : [(String, (SPARQLParserWindowTests) -> () throws -> Void)] { 8 | return [ 9 | ("testRank", testRank), 10 | ("testSerialization", testSerialization), 11 | ("testWindowHaving", testWindowHaving), 12 | ("testWindowAggregation", testWindowAggregation), 13 | ] 14 | } 15 | } 16 | #endif 17 | 18 | // swiftlint:disable type_body_length 19 | class SPARQLParserWindowTests: XCTestCase { 20 | 21 | override func setUp() { 22 | super.setUp() 23 | // Put setup code here. This method is called before the invocation of each test method in the class. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testSerialization() { 32 | guard var p = SPARQLParser(string: "SELECT (RANK() OVER (PARTITION BY ?s ?o ORDER BY ?o RANGE BETWEEN 3 following AND current row) AS ?rank) WHERE { ?s ?p ?o }") else { XCTFail(); return } 33 | do { 34 | let q = try p.parseQuery() 35 | let s = SPARQLSerializer() 36 | let sparql = try s.serialize(q.sparqlTokens()) 37 | XCTAssertEqual(sparql, "SELECT ( RANK ( ) OVER ( PARTITION BY ?s ?o ORDER BY ?o RANGE BETWEEN \"3\" ^^ FOLLOWING AND CURRENT ROW ) AS ?rank ) WHERE { ?s ?p ?o . }") 38 | } catch let e { 39 | XCTFail("\(e)") 40 | } 41 | } 42 | 43 | func testWindowAggregation() throws { 44 | let sparql = """ 45 | PREFIX : 46 | SELECT (AVG(?value) OVER (ORDER BY ?date ROWS BETWEEN 3 PRECEDING AND CURRENT ROW) AS ?movingAverage) WHERE { 47 | VALUES (?date ?value) { 48 | (1 1.0) # 1.0 49 | (2 2.0) # 1.5 50 | (3 3.0) # 2.0 51 | (4 2.0) # 2.33 52 | (5 0.0) # 2.5 53 | (6 0.0) # 0.66 54 | (7 1.0) # 0.33 55 | } 56 | } 57 | """ 58 | guard var p = SPARQLParser(string: sparql) else { XCTFail(); return } 59 | let a = try p.parseAlgebra() 60 | XCTAssertEqual(a.inscope, ["movingAverage"]) 61 | guard case let .project( 62 | .window(_, _), 63 | projection 64 | ) = a else { 65 | XCTFail("Unexpected algebra: \(a.serialize())") 66 | return 67 | } 68 | XCTAssertEqual(projection, ["movingAverage"]) 69 | } 70 | 71 | func testWindowHaving() throws { 72 | let sparql = """ 73 | PREFIX foaf: 74 | SELECT ?name ?o WHERE { 75 | ?s a foaf:Person ; 76 | foaf:name ?name ; 77 | foaf:schoolHomepage ?o 78 | } 79 | HAVING (RANK() OVER (PARTITION BY ?s) < 2) 80 | """ 81 | guard var p = SPARQLParser(string: sparql) else { XCTFail(); return } 82 | let a = try p.parseAlgebra() 83 | XCTAssertEqual(a.inscope, ["name", "o"]) 84 | guard case let .project( 85 | .filter( 86 | .window(_, _), 87 | _ 88 | ), 89 | projection 90 | ) = a else { 91 | XCTFail("Unexpected algebra: \(a.serialize())") 92 | return 93 | } 94 | XCTAssertEqual(projection, ["name", "o"]) 95 | } 96 | 97 | func testRank() { 98 | guard var p = SPARQLParser(string: "SELECT ?s ?p ?o (RANK() OVER (PARTITION BY ?s ?p ORDER BY ?o) AS ?rank) WHERE { ?s ?p ?o } ORDER BY ?rank") else { XCTFail(); return } 99 | do { 100 | let a = try p.parseAlgebra() 101 | let expectedMapping = Algebra.WindowFunctionMapping( 102 | windowApplication: WindowApplication( 103 | windowFunction: .rank, 104 | comparators: [ 105 | Algebra.SortComparator( 106 | ascending: true, 107 | expression: .node(.variable("o", binding: true))) 108 | ], 109 | partition: [.node(.variable("s", binding: true)), .node(.variable("p", binding: true))], 110 | frame: WindowFrame( 111 | type: .rows, 112 | from: .unbound, 113 | to: .unbound 114 | ) 115 | ), 116 | variableName: "rank" 117 | ) 118 | guard case let .project( 119 | .order( 120 | .window(child, m), 121 | _ 122 | ), 123 | projection 124 | ) = a else { 125 | XCTFail("Unexpected algebra: \(a.serialize())") 126 | return 127 | } 128 | 129 | XCTAssertEqual(m.count, 1) 130 | let gotMapping = m[0] 131 | XCTAssertEqual(gotMapping, expectedMapping) 132 | XCTAssertEqual(gotMapping.variableName, "rank") 133 | let app = gotMapping.windowApplication 134 | XCTAssertEqual(app.comparators.count, 1) 135 | XCTAssertEqual(app.partition.count, 2) 136 | XCTAssertEqual(projection, ["s", "p", "o", "rank"]) 137 | XCTAssertEqual(a.inscope, ["s", "p", "o", "rank"]) 138 | XCTAssertEqual(Algebra.window(child, m).inscope, ["s", "p", "o", "rank"]) 139 | } catch let e { 140 | XCTFail("\(e)") 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /Tests/SPARQLSyntaxTests/SPARQLReformattingTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Foundation 3 | @testable import SPARQLSyntax 4 | 5 | #if os(Linux) 6 | extension SPARQLReformattingTests { 7 | static var allTests : [(String, (SPARQLReformattingTests) -> () throws -> Void)] { 8 | return [ 9 | ("testReformat_extraContent", testReformat_extraContent), 10 | ("testReformat_invalidToken", testReformat_invalidToken), 11 | ("testReformat_plain", testReformat_plain), 12 | ("testReformat_pretty", testReformat_pretty), 13 | ] 14 | } 15 | } 16 | #endif 17 | 18 | // swiftlint:disable type_body_length 19 | class SPARQLReformattingTests: XCTestCase { 20 | 21 | override func setUp() { 22 | super.setUp() 23 | // Put setup code here. This method is called before the invocation of each test method in the class. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testReformat_plain() throws { 32 | let sparql = """ 33 | prefix geo: 34 | select ?s 35 | where{ 36 | ?s geo:lat ?lat ;geo:long+ ?long ; # foo bar 37 | FILTER(?long < -117.0) 38 | FILTER(?lat >= 31.0) 39 | FILTER(?lat <= 33.0) 40 | } ORDER BY DESC(?s) 41 | """ 42 | let s = SPARQLSerializer() 43 | let l = s.reformat(sparql) 44 | let expected = """ 45 | PREFIX geo: SELECT ?s WHERE { ?s geo:lat ?lat ; geo:long + ?long ; # foo bar 46 | FILTER ( ?long < - 117.0 ) FILTER ( ?lat >= 31.0 ) FILTER ( ?lat <= 33.0 ) } ORDER BY DESC ( ?s ) 47 | """ 48 | XCTAssertEqual(l, expected) 49 | } 50 | 51 | func testReformat_pretty() throws { 52 | let sparql = """ 53 | prefix geo: 54 | select ?s 55 | where{ 56 | ?s geo:lat ?lat ;geo:long+ ?long ; # foo bar 57 | FILTER(?long < -117.0) 58 | FILTER(?lat >= 31.0) 59 | FILTER(?lat <= 33.0) 60 | } ORDER BY DESC ( ?s) 61 | """ 62 | let s = SPARQLSerializer(prettyPrint: true) 63 | let l = s.reformat(sparql) 64 | // print(l) 65 | let expected = """ 66 | PREFIX geo: 67 | SELECT ?s WHERE { 68 | ?s geo:lat ?lat ; 69 | geo:long+ ?long ; 70 | # foo bar 71 | FILTER(?long < - 117.0) 72 | FILTER(?lat >= 31.0) 73 | FILTER(?lat <= 33.0) 74 | } 75 | ORDER BY DESC(?s) 76 | 77 | """ 78 | XCTAssertEqual(l, expected) 79 | } 80 | 81 | func testReformat_invalidToken() throws { 82 | let sparql = """ 83 | prefix geo: 84 | select ?s 85 | where{ 86 | ?s geo:lat lat ;geo:long+ ?long ; # foo bar 87 | FILTER(?long < -117.0) 88 | FILTER(?lat >= 31.0) 89 | FILTER(?lat <= 33.0) 90 | } ORDER BY DESC ( ?s) 91 | """ 92 | let s = SPARQLSerializer(prettyPrint: true) 93 | let l = s.reformat(sparql) 94 | // print(l) 95 | let expected = """ 96 | PREFIX geo: 97 | SELECT ?s WHERE { 98 | ?s geo:lat lat ;geo:long+ ?long ; # foo bar 99 | FILTER(?long < -117.0) 100 | FILTER(?lat >= 31.0) 101 | FILTER(?lat <= 33.0) 102 | } ORDER BY DESC ( ?s) 103 | """ 104 | XCTAssertEqual(l, expected) 105 | } 106 | 107 | func testReformat_extraContent() throws { 108 | let sparql = """ 109 | prefix geo: 110 | select ?s 111 | where{ 112 | ?s geo:lat ?lat ;geo:long+ ?long ; # foo bar 113 | FILTER(?long < -117.0) 114 | FILTER(?lat >= 31.0) 115 | FILTER(?lat <= 33.0) 116 | } ORDER BY DESC ( ?s) 117 | foo' 118 | bar 119 | """ 120 | let s = SPARQLSerializer(prettyPrint: true) 121 | let l = s.reformat(sparql) 122 | // print(l) 123 | let expected = """ 124 | PREFIX geo: 125 | SELECT ?s WHERE { 126 | ?s geo:lat ?lat ; 127 | geo:long+ ?long ; 128 | # foo bar 129 | FILTER(?long < - 117.0) 130 | FILTER(?lat >= 31.0) 131 | FILTER(?lat <= 33.0) 132 | } 133 | ORDER BY DESC(?s) 134 | foo' 135 | bar 136 | """ 137 | XCTAssertEqual(l, expected) 138 | } 139 | 140 | func testReformat_extraContent2() throws { 141 | let sparql = "select ?s where{ ?s ?p ?o}ORDER BY DESC ( ?s)\"" 142 | let s = SPARQLSerializer(prettyPrint: true) 143 | let l = s.reformat(sparql) 144 | let expected = """ 145 | SELECT ?s WHERE { 146 | ?s ?p ?o 147 | } 148 | ORDER BY DESC(?s)" 149 | """ 150 | XCTAssertEqual(l, expected) 151 | XCTAssertTrue(l.contains(#/"/#)) 152 | } 153 | 154 | func testReformat_delete() throws { 155 | let sparql = """ 156 | prefix geo: 157 | delete {?s ?p ?o} 158 | where{ 159 | ?s geo:lat ?lat ;geo:long ?long FILTER(?long < -117.0) FILTER(?lat >= 31.0)FILTER(?lat <= 33.0) 160 | } 161 | """ 162 | let s = SPARQLSerializer(prettyPrint: true) 163 | let l = s.reformat(sparql) 164 | // print(l) 165 | let expected = """ 166 | PREFIX geo: 167 | DELETE { 168 | ?s ?p ?o 169 | } 170 | WHERE { 171 | ?s geo:lat ?lat ; 172 | geo:long ?long 173 | FILTER(?long < - 117.0) 174 | FILTER(?lat >= 31.0) 175 | FILTER(?lat <= 33.0) 176 | } 177 | 178 | """ 179 | XCTAssertEqual(l, expected) 180 | } 181 | 182 | func testReformat_complexUpdate() throws { 183 | let sparql = """ 184 | drop silent named;with DELETE { ?a ?b ?c } insert{ ?c?b ?a } WHERE { ?a 185 | ?b ?c } ; 186 | COPY 187 | default 188 | to 189 | """ 190 | let s = SPARQLSerializer(prettyPrint: true) 191 | let l = s.reformat(sparql) 192 | // print(l) 193 | let expected = """ 194 | DROP SILENT NAMED ; 195 | WITH 196 | DELETE { 197 | ?a ?b ?c 198 | } 199 | INSERT { 200 | ?c ?b ?a 201 | } 202 | WHERE { 203 | ?a ?b ?c 204 | } 205 | ; 206 | COPY DEFAULT TO 207 | 208 | """ 209 | XCTAssertEqual(l, expected) 210 | } 211 | 212 | func testReformat_updateSubqueryProjection() throws { 213 | let sparql = """ 214 | DELETE { ?a ?b ?c } WHERE { 215 | { ?a

?b ; ?c } UNION { 216 | SELECT ?a ?b ?c WHERE { ?a ?b ?c }}} 217 | """ 218 | let s = SPARQLSerializer(prettyPrint: true) 219 | let l = s.reformat(sparql) 220 | let expected = """ 221 | DELETE { 222 | ?a ?b ?c 223 | } 224 | WHERE { 225 | { 226 | ?a

?b ; 227 | ?c 228 | } UNION { 229 | SELECT ?a ?b ?c WHERE { 230 | ?a ?b ?c 231 | } 232 | } 233 | } 234 | 235 | """ 236 | XCTAssertEqual(l, expected) 237 | } 238 | 239 | func testReformat_filterWithNewlineFromComment() throws { 240 | let sparql = """ 241 | select * where { 242 | FILTER(#comment 243 | true) 244 | } 245 | """ 246 | let s = SPARQLSerializer(prettyPrint: true) 247 | let l = s.reformat(sparql) 248 | let expected = """ 249 | SELECT * WHERE { 250 | FILTER(# comment 251 | true) 252 | } 253 | 254 | """ 255 | XCTAssertEqual(l, expected) 256 | } 257 | 258 | func testReformat_tripleAfterBind() throws { 259 | let sparql = """ 260 | select * where { bind (1 AS ?x) ?s ?p ?o } 261 | """ 262 | let s = SPARQLSerializer(prettyPrint: true) 263 | let l = s.reformat(sparql) 264 | 265 | let expected = """ 266 | SELECT * WHERE { 267 | BIND(1 AS ?x) 268 | ?s ?p ?o 269 | } 270 | 271 | """ 272 | XCTAssertEqual(l, expected) 273 | } 274 | 275 | func testReformat_tripleAfterBrackettedFilter() throws { 276 | let sparql = """ 277 | select * where { filter (true) ?s ?p ?o } 278 | """ 279 | let s = SPARQLSerializer(prettyPrint: true) 280 | let l = s.reformat(sparql) 281 | 282 | let expected = """ 283 | SELECT * WHERE { 284 | FILTER(true) 285 | ?s ?p ?o 286 | } 287 | 288 | """ 289 | XCTAssertEqual(l, expected) 290 | } 291 | 292 | func testReformat_tripleAfterBareFunctionFilter() throws { 293 | let sparql = """ 294 | select * where { filter (true) ?s ?p ?o } 295 | """ 296 | let s = SPARQLSerializer(prettyPrint: true) 297 | let l = s.reformat(sparql) 298 | 299 | let expected = """ 300 | SELECT * WHERE { 301 | FILTER (true) 302 | ?s ?p ?o 303 | } 304 | 305 | """ 306 | XCTAssertEqual(l, expected) 307 | } 308 | 309 | func testReformat_tripleAfterBareFunctionFilterEmptyArgs() throws { 310 | let sparql = """ 311 | select * where { filter () ?s ?p ?o } 312 | """ 313 | let s = SPARQLSerializer(prettyPrint: true) 314 | let l = s.reformat(sparql) 315 | 316 | let expected = """ 317 | SELECT * WHERE { 318 | FILTER () 319 | ?s ?p ?o 320 | } 321 | 322 | """ 323 | XCTAssertEqual(l, expected) 324 | } 325 | 326 | func testReformat_tripleAfterBuiltInFilter() throws { 327 | let sparql = """ 328 | select * where { filter CONTAINS(?x, ?y) ?s ?p ?o } 329 | """ 330 | let s = SPARQLSerializer(prettyPrint: true) 331 | let l = s.reformat(sparql) 332 | 333 | let expected = """ 334 | SELECT * WHERE { 335 | FILTER CONTAINS(?x , ?y) 336 | ?s ?p ?o 337 | } 338 | 339 | """ 340 | XCTAssertEqual(l, expected) 341 | } 342 | 343 | func testReformat_tripleAfterBuiltInFilterEmptyArgs() throws { 344 | let sparql = """ 345 | select * where { filter BNODE() ?s ?p ?o } 346 | """ 347 | let s = SPARQLSerializer(prettyPrint: true) 348 | let l = s.reformat(sparql) 349 | 350 | let expected = """ 351 | SELECT * WHERE { 352 | FILTER BNODE () 353 | ?s ?p ?o 354 | } 355 | 356 | """ 357 | XCTAssertEqual(l, expected) 358 | } 359 | 360 | func testReformat_tripleAfterNestedFilterInExists() throws { 361 | let sparql = """ 362 | select * where { 363 | ?x ?y ?z 364 | filter( 365 | ?z && NOT EXISTS { 366 | ?x 367 | filter(true) ?x ?rr 368 | } 369 | ) ?s ?p ?o 370 | } 371 | """ 372 | let s = SPARQLSerializer(prettyPrint: true) 373 | let l = s.reformat(sparql) 374 | 375 | let expected = """ 376 | SELECT * WHERE { 377 | ?x ?y ?z 378 | FILTER(?z && NOT EXISTS { 379 | ?x 380 | FILTER(true) 381 | ?x ?rr 382 | } 383 | ) 384 | ?s ?p ?o 385 | } 386 | 387 | """ 388 | XCTAssertEqual(l, expected) 389 | } 390 | 391 | func testReformat_values() throws { 392 | let sparql = """ 393 | select * where { values ?x { 1 2 3 } } 394 | """ 395 | let s = SPARQLSerializer(prettyPrint: true) 396 | let l = s.reformat(sparql) 397 | 398 | let expected = """ 399 | SELECT * WHERE { 400 | VALUES ?x 401 | { 402 | 1 2 3 403 | } 404 | } 405 | 406 | """ 407 | XCTAssertEqual(l, expected) 408 | } 409 | 410 | func testReformat_values_after_triple() throws { 411 | let sparql = """ 412 | select * where { ?s ?p ?o values ?x { 1 2 3 } } 413 | """ 414 | let s = SPARQLSerializer(prettyPrint: true) 415 | let l = s.reformat(sparql) 416 | 417 | let expected = """ 418 | SELECT * WHERE { 419 | ?s ?p ?o 420 | VALUES ?x 421 | { 422 | 1 2 3 423 | } 424 | } 425 | 426 | """ 427 | XCTAssertEqual(l, expected) 428 | } 429 | 430 | func testReformat_prefixedname_with_underscores() throws { 431 | let sparql = """ 432 | prefiX ex: 433 | select * where { ?s ex:foo_bar ?o } 434 | """ 435 | let s = SPARQLSerializer(prettyPrint: true) 436 | let l = s.reformat(sparql) 437 | 438 | let expected = """ 439 | PREFIX ex: 440 | SELECT * WHERE { 441 | ?s ex:foo_bar ?o 442 | } 443 | 444 | """ 445 | XCTAssertEqual(l, expected) 446 | } 447 | 448 | func testReformat_propertyPath() throws { 449 | let sparql = """ 450 | prefix ex: 451 | select * where { ?s (ex:foo+/ex:bar)* ?o } 452 | """ 453 | let s = SPARQLSerializer(prettyPrint: true) 454 | let l = s.reformat(sparql) 455 | 456 | let expected = """ 457 | PREFIX ex: 458 | SELECT * WHERE { 459 | ?s (ex:foo+/ex:bar)* ?o 460 | } 461 | 462 | """ 463 | XCTAssertEqual(l, expected) 464 | } 465 | 466 | func testReformat_propertyPath2() throws { 467 | // Token lookahead and paren depth isn't enough state to tell that the property star 468 | // in this query is part of a property path (and so shouldn't have leading whitespace) 469 | // as opposed to part of a numeric expression. 470 | let sparql = """ 471 | prefix ex: 472 | select * where { ?s ((ex:foo/ex:bar)*/ex:baz) ?o } 473 | """ 474 | let s = SPARQLSerializer(prettyPrint: true) 475 | let l = s.reformat(sparql) 476 | 477 | let expected = """ 478 | PREFIX ex: 479 | SELECT * WHERE { 480 | ?s ((ex:foo/ex:bar) */ex:baz) ?o 481 | } 482 | 483 | """ 484 | XCTAssertEqual(l, expected) 485 | } 486 | 487 | func testReformat_prefix_base() throws { 488 | let sparql = """ 489 | prefix ex1: base prefix ex2: 490 | select * from where { ?s ex1:foo ?o } 491 | """ 492 | let s = SPARQLSerializer(prettyPrint: true) 493 | let l = s.reformat(sparql) 494 | 495 | let expected = """ 496 | PREFIX ex1: 497 | BASE 498 | PREFIX ex2: 499 | SELECT * 500 | FROM 501 | WHERE { 502 | ?s ex1:foo ?o 503 | } 504 | 505 | """ 506 | XCTAssertEqual(l, expected) 507 | } 508 | 509 | func testReformat_groupBy_orderBy() throws { 510 | let sparql = """ 511 | prefix ex: 512 | select * where { ?s1 ex:foo ?o } group by ?s1 order by ?s1 513 | 514 | """ 515 | let s = SPARQLSerializer(prettyPrint: true) 516 | let l = s.reformat(sparql) 517 | 518 | let expected = """ 519 | PREFIX ex: 520 | SELECT * WHERE { 521 | ?s1 ex:foo ?o 522 | } 523 | GROUP BY ?s1 524 | ORDER BY ?s1 525 | 526 | """ 527 | XCTAssertEqual(l, expected) 528 | } 529 | 530 | func testReformat_propertyPathOperators() throws { 531 | let sparql = """ 532 | SELECT DISTINCT ?work ?date 533 | WHERE 534 | { ?work ex:related|^ex:related . 535 | ?work ex:date ?date 536 | } 537 | ORDER BY DESC(?date) 538 | 539 | """ 540 | let s = SPARQLSerializer(prettyPrint: true) 541 | let l = s.reformat(sparql) 542 | 543 | let expected = """ 544 | SELECT DISTINCT ?work ?date WHERE { 545 | ?work ex:related|^ex:related . 546 | ?work ex:date ?date 547 | } 548 | ORDER BY DESC(?date) 549 | 550 | """ 551 | XCTAssertEqual(l, expected) 552 | } 553 | 554 | func testReformat_pname() throws { 555 | let sparql = """ 556 | prefix ex: 557 | select * where { ?s ex:foo-bar ?o } 558 | 559 | """ 560 | let s = SPARQLSerializer(prettyPrint: true) 561 | let l = s.reformat(sparql) 562 | 563 | let expected = """ 564 | PREFIX ex: 565 | SELECT * WHERE { 566 | ?s ex:foo-bar ?o 567 | } 568 | 569 | """ 570 | XCTAssertEqual(l, expected) 571 | } 572 | 573 | func testReformat_tripleOptional() throws { 574 | let sparql = """ 575 | select * where { 576 | ?s ?p ?o OPTIONAL { ?s 1. } 577 | } 578 | 579 | """ 580 | let s = SPARQLSerializer(prettyPrint: true) 581 | let l = s.reformat(sparql) 582 | 583 | let expected = """ 584 | SELECT * WHERE { 585 | ?s ?p ?o 586 | OPTIONAL { 587 | ?s 1 . 588 | } 589 | } 590 | 591 | """ 592 | XCTAssertEqual(l, expected) 593 | } 594 | 595 | 596 | // TODO: test a filter nested in another filter via an EXISTS block 597 | } 598 | -------------------------------------------------------------------------------- /Tests/SPARQLSyntaxTests/SPARQLRewriting.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Foundation 3 | import SPARQLSyntax 4 | 5 | #if os(Linux) 6 | extension SPARQLParserTests { 7 | static var allTests : [(String, (SPARQLParserTest) -> () throws -> Void)] { 8 | return [ 9 | ("testMultipleRewrite", testMultipleRewrite), 10 | ("testNodeReplacement", testNodeReplacement), 11 | ("testUpwardsRewrite", testUpwardsRewrite), 12 | ] 13 | } 14 | } 15 | #endif 16 | 17 | // swiftlint:disable type_body_length 18 | class SPARQLNodeReplacementTests: XCTestCase { 19 | 20 | override func setUp() { 21 | super.setUp() 22 | // Put setup code here. This method is called before the invocation of each test method in the class. 23 | } 24 | 25 | override func tearDown() { 26 | // Put teardown code here. This method is called after the invocation of each test method in the class. 27 | super.tearDown() 28 | } 29 | 30 | func testTermReplacement() { 31 | guard var p = SPARQLParser(string: "PREFIX ex: SELECT * WHERE {\n_:s ex:value ?o . FILTER(?o != 7.0)\n}\n") else { XCTFail(); return } 32 | do { 33 | let a = try p.parseAlgebra() 34 | let replaced = try a.replace(["o": Term(integer: 8)]) 35 | let tp = TriplePattern( 36 | subject: .variable(".blank.b1", binding: false), 37 | predicate: .bound(Term(iri: "http://example.org/value")), 38 | object: .bound(Term(integer: 8)) 39 | ) 40 | guard case .filter( 41 | let pattern, 42 | .ne( 43 | .node(.bound(Term(integer: 8))), 44 | .node(.bound(Term(value: "7.0", type: .datatype(.decimal)))) 45 | )) = replaced else { 46 | XCTFail("Unexpected algebra: \(a.serialize())") 47 | return 48 | } 49 | 50 | guard case .triple(let got) = pattern else { 51 | XCTFail("Unexpected algebra: \(pattern.serialize())") 52 | return 53 | } 54 | 55 | XCTAssertEqual(got, tp) 56 | } catch let e { 57 | XCTFail("\(e)") 58 | } 59 | } 60 | 61 | func testNodeReplacement() { 62 | guard var p = SPARQLParser(string: "PREFIX ex: SELECT * WHERE {\n_:s ex:value ?o . FILTER(?o != 7.0)\n}\n") else { XCTFail(); return } 63 | do { 64 | let a = try p.parseAlgebra() 65 | let replaced = try a.replace(["o": .variable("xyz", binding: true)]) 66 | let tp = TriplePattern( 67 | subject: .variable(".blank.b1", binding: false), 68 | predicate: .bound(Term(iri: "http://example.org/value")), 69 | object: .variable("xyz", binding: true) 70 | ) 71 | guard case .filter( 72 | let pattern, 73 | .ne( 74 | .node(.variable("xyz", binding: true)), 75 | .node(.bound(Term(value: "7.0", type: .datatype(.decimal)))) 76 | )) = replaced else { 77 | XCTFail("Unexpected algebra: \(a.serialize())") 78 | return 79 | } 80 | 81 | guard case .triple(let got) = pattern else { 82 | XCTFail("Unexpected algebra: \(pattern.serialize())") 83 | return 84 | } 85 | 86 | XCTAssertEqual(got, tp) 87 | } catch let e { 88 | XCTFail("\(e)") 89 | } 90 | } 91 | 92 | func testMultipleRewrite() { 93 | // the use of rewrite() here first replaces the variable name in .extend(_, _, name) with "XXX", 94 | // and then rewrites the extend's child algebra (a .triple) with a .bgp containing a triple 95 | // with a different object value 96 | guard var p = SPARQLParser(string: "PREFIX ex: SELECT * WHERE {\n_:s ex:value 7 . BIND(1 AS ?s)\n}\n") else { XCTFail(); return } 97 | do { 98 | let a = try p.parseAlgebra() 99 | let replaced = try a.rewrite({ (a) -> RewriteStatus in 100 | switch a { 101 | case let .extend(a, e, _): 102 | return .rewriteChildren(.extend(a, e, "XXX")) 103 | case .triple(let tp): 104 | let p = TriplePattern(subject: tp.subject, predicate: tp.predicate, object: .bound(Term(integer: 8))) 105 | return .rewriteChildren(.bgp([p])) 106 | default: 107 | return .rewriteChildren(a) 108 | } 109 | }) 110 | let tp = TriplePattern( 111 | subject: .variable(".blank.b1", binding: false), 112 | predicate: .bound(Term(iri: "http://example.org/value")), 113 | object: .bound(Term(integer: 8)) 114 | ) 115 | guard case .extend(.bgp(let got), _, "XXX") = replaced else { 116 | XCTFail("Unexpected algebra: \(replaced.serialize())") 117 | return 118 | } 119 | 120 | XCTAssertEqual(got[0], tp) 121 | } catch let e { 122 | XCTFail("\(e)") 123 | } 124 | } 125 | 126 | func testUpwardsRewrite() { 127 | // rewrite .triple(_) to .unionIdentity, and then see that it gets propogated upwards 128 | // to collapse the entire algebra into a single .unionIdentity 129 | guard var p = SPARQLParser(string: "PREFIX ex: SELECT DISTINCT * WHERE {\n_:s ex:value 7 . BIND(1 AS ?s)\n}\n") else { XCTFail(); return } 130 | do { 131 | let a = try p.parseAlgebra() 132 | let replaced = try a.rewrite({ (a) -> RewriteStatus in 133 | switch a { 134 | case .distinct(.unionIdentity): 135 | return .rewriteChildren(.unionIdentity) 136 | case .extend(.unionIdentity, _, _): 137 | return .rewriteChildren(.unionIdentity) 138 | case .triple(_): 139 | return .rewriteChildren(.unionIdentity) 140 | default: 141 | return .rewriteChildren(a) 142 | } 143 | }) 144 | guard case .unionIdentity = replaced else { 145 | XCTFail("Unexpected algebra: \(replaced.serialize())") 146 | return 147 | } 148 | } catch let e { 149 | XCTFail("\(e)") 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /Tests/SPARQLSyntaxTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !os(macOS) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(AlgebraTests.allTests), 7 | testCase(IRITests.allTests), 8 | testCase(RDFTests.allTests), 9 | testCase(SPARQLParserTests.allTests), 10 | testCase(SPARQLReformattingTests.allTests), 11 | testCase(SPARQLRewritingTests.allTests), 12 | testCase(SPARQLSerializationTests.allTests), 13 | ] 14 | } 15 | #endif 16 | -------------------------------------------------------------------------------- /Tests/SPARQLSyntaxTests/XSDTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import Foundation 3 | @testable import SPARQLSyntax 4 | 5 | #if os(Linux) 6 | extension XSDTest { 7 | static var allTests : [(String, (XSDTest) -> () throws -> Void)] { 8 | return [ 9 | ("testInteger", testInteger), 10 | ("testNegativeFloat", testNegativeFloat), 11 | ("testPositiveFloat", testPositiveFloat), 12 | ("testPositiveDouble", testPositiveDouble), 13 | ("testPositiveDecimal", testPositiveDecimal), 14 | ("testAddition", testAddition), 15 | ("testSubtraction", testSubtraction), 16 | ("testMultiplication", testMultiplication), 17 | ("testDivision", testDivision), 18 | ("testNegate", testNegate), 19 | ("testEquality", testEquality), 20 | ] 21 | } 22 | } 23 | #endif 24 | 25 | // swiftlint:disable type_body_length 26 | class XSDTest: XCTestCase { 27 | 28 | override func setUp() { 29 | super.setUp() 30 | // Put setup code here. This method is called before the invocation of each test method in the class. 31 | } 32 | 33 | override func tearDown() { 34 | // Put teardown code here. This method is called after the invocation of each test method in the class. 35 | super.tearDown() 36 | } 37 | 38 | func testInteger() { 39 | let value = NumericValue.integer(-7) 40 | XCTAssertEqual(value.value, -7.0) 41 | XCTAssertEqual(value.absoluteValue.value, 7.0) 42 | XCTAssertEqual(value.round.value, -7.0) 43 | XCTAssertEqual(value.ceil.value, -7.0) 44 | XCTAssertEqual(value.floor.value, -7.0) 45 | XCTAssertEqual(value.term, Term(integer: -7)) 46 | XCTAssertEqual(value.description, "-7") 47 | } 48 | 49 | func testNegativeFloat() { 50 | let value = NumericValue.float(mantissa: -35, exponent: -1) 51 | XCTAssertEqual(value.value, -3.5) 52 | XCTAssertEqual(value.absoluteValue.value, 3.5) 53 | XCTAssertEqual(value.round.value, -4.0) 54 | XCTAssertEqual(value.ceil.value, -3.0) 55 | XCTAssertEqual(value.floor.value, -4.0) 56 | XCTAssertEqual(value.term, Term(float: -3.5)) 57 | XCTAssertEqual(value.description, "-35.0E-1f") 58 | } 59 | 60 | func testPositiveFloat() { 61 | let value = NumericValue.float(mantissa: 15, exponent: -1) 62 | XCTAssertEqual(value.value, 1.5) 63 | XCTAssertEqual(value.absoluteValue.value, 1.5) 64 | XCTAssertEqual(value.round.value, 2.0) 65 | XCTAssertEqual(value.ceil.value, 2.0) 66 | XCTAssertEqual(value.floor.value, 1.0) 67 | XCTAssertEqual(value.term, Term(float: 1.5)) 68 | XCTAssertEqual(value.description, "15.0E-1f") 69 | } 70 | 71 | func testPositiveDouble() { 72 | let value = NumericValue.double(mantissa: 15, exponent: -1) 73 | XCTAssertEqual(value.value, 1.5) 74 | XCTAssertEqual(value.absoluteValue.value, 1.5) 75 | XCTAssertEqual(value.round.value, 2.0) 76 | XCTAssertEqual(value.ceil.value, 2.0) 77 | XCTAssertEqual(value.floor.value, 1.0) 78 | XCTAssertEqual(value.term, Term(double: 1.5)) 79 | XCTAssertEqual(value.description, "15.0E-1d") 80 | } 81 | 82 | func testPositiveDecimal() { 83 | let value = NumericValue.decimal(Decimal(string: "1.123")!) 84 | XCTAssertEqual(value.value, 1.123) 85 | XCTAssertEqual(value.absoluteValue.value, 1.123) 86 | XCTAssertEqual(value.round.value, 1.0) 87 | XCTAssertEqual(value.ceil.value, 2.0) 88 | XCTAssertEqual(value.floor.value, 1.0) 89 | XCTAssertEqual(value.term, Term(decimal: Decimal(string: "1.123")!)) 90 | XCTAssertEqual(value.description, "1.123") 91 | } 92 | 93 | func testAddition() { 94 | let i = NumericValue.integer(-1) 95 | let f = NumericValue.float(mantissa: 15, exponent: -1) 96 | let d = NumericValue.double(mantissa: 25, exponent: 2) 97 | let c = NumericValue.decimal(Decimal(string: "1.123")!) 98 | 99 | let iiValue = i + i 100 | if case .integer(let v) = iiValue { 101 | XCTAssertEqual(v, -2) 102 | XCTAssertEqual(iiValue.value, -2.0) 103 | } else { 104 | XCTFail() 105 | } 106 | 107 | let ifValue = i + f 108 | if case .float(_, _) = ifValue { // case let .float(m, e) 109 | // XCTAssertEqual(m, 5) 110 | // XCTAssertEqual(e, -1) 111 | XCTAssertEqual(ifValue.value, 0.5) 112 | } else { 113 | XCTFail() 114 | } 115 | 116 | let idValue = i + d 117 | if case .double(_, _) = idValue { // case let .double(m, e) 118 | XCTAssertEqual(idValue.value, 2499.0) 119 | } else { 120 | XCTFail() 121 | } 122 | 123 | let icValue = i + c 124 | if case .decimal(_) = icValue { // case let .decimal(d) 125 | XCTAssertEqual(icValue.value, 0.123) 126 | } else { 127 | XCTFail("\(icValue)") 128 | } 129 | 130 | let dfValue = d + f 131 | if case .double(_, _) = dfValue { // case let .double(m, e) 132 | XCTAssertEqual(dfValue.value, 2501.5) 133 | } else { 134 | XCTFail() 135 | } 136 | } 137 | 138 | func testSubtraction() { 139 | let i = NumericValue.integer(-1) 140 | let f = NumericValue.float(mantissa: 15, exponent: -1) 141 | let d = NumericValue.double(mantissa: 25, exponent: 2) 142 | let c = NumericValue.decimal(Decimal(string: "1.123")!) 143 | 144 | let iiValue = i - i 145 | if case .integer(let v) = iiValue { 146 | XCTAssertEqual(v, 0) 147 | XCTAssertEqual(iiValue.value, 0.0) 148 | } else { 149 | XCTFail() 150 | } 151 | 152 | let ifValue = i - f 153 | if case .float(_, _) = ifValue { // case let .float(m, e) 154 | // XCTAssertEqual(m, 5) 155 | // XCTAssertEqual(e, -1) 156 | XCTAssertEqual(ifValue.value, -2.5) 157 | } else { 158 | XCTFail() 159 | } 160 | 161 | let idValue = i - d 162 | if case .double(_, _) = idValue { // case let .double(m, e) 163 | XCTAssertEqual(idValue.value, -2501.0) 164 | } else { 165 | XCTFail() 166 | } 167 | 168 | let icValue = i - c 169 | if case .decimal(_) = icValue { // case let .decimal(d) 170 | XCTAssertEqual(icValue.value, -2.123, accuracy: 0.0001) 171 | } else { 172 | XCTFail("\(icValue)") 173 | } 174 | 175 | let dfValue = d - f 176 | if case .double(_, _) = dfValue { // case let .double(m, e) 177 | XCTAssertEqual(dfValue.value, 2498.5, accuracy: 0.0001) 178 | } else { 179 | XCTFail() 180 | } 181 | } 182 | 183 | func testMultiplication() { 184 | let i = NumericValue.integer(-1) 185 | let f = NumericValue.float(mantissa: 15, exponent: -1) 186 | let d = NumericValue.double(mantissa: 25, exponent: 2) 187 | let c = NumericValue.decimal(Decimal(string: "1.123")!) 188 | 189 | let iiValue = i * i 190 | if case .integer(let v) = iiValue { 191 | XCTAssertEqual(v, 1) 192 | XCTAssertEqual(iiValue.value, 1.0) 193 | } else { 194 | XCTFail() 195 | } 196 | 197 | let ifValue = i * f 198 | if case .float(_, _) = ifValue { // case let .float(m, e) 199 | // XCTAssertEqual(m, 5) 200 | // XCTAssertEqual(e, -1) 201 | XCTAssertEqual(ifValue.value, -1.5) 202 | } else { 203 | XCTFail() 204 | } 205 | 206 | let idValue = i * d 207 | if case .double(_, _) = idValue { // case let .double(m, e) 208 | XCTAssertEqual(idValue.value, -2500.0) 209 | } else { 210 | XCTFail() 211 | } 212 | 213 | let icValue = i * c 214 | if case .decimal(_) = icValue { // case let .decimal(d) 215 | XCTAssertEqual(icValue.value, -1.123, accuracy: 0.0001) 216 | } else { 217 | XCTFail("\(icValue)") 218 | } 219 | 220 | let dfValue = d * f 221 | if case .double(_, _) = dfValue { // case let .double(m, e) 222 | XCTAssertEqual(dfValue.value, 3750.0, accuracy: 0.0001) 223 | } else { 224 | XCTFail() 225 | } 226 | } 227 | 228 | func testDivision() { 229 | let i = NumericValue.integer(-1) 230 | let f = NumericValue.float(mantissa: 15, exponent: -1) 231 | let d = NumericValue.double(mantissa: 25, exponent: 2) 232 | let c = NumericValue.decimal(Decimal(string: "1.123")!) 233 | 234 | let iiValue = i / i 235 | if case .decimal(_) = iiValue { 236 | XCTAssertEqual(iiValue.value, 1.0, accuracy: 0.0001) 237 | } else { 238 | XCTFail() 239 | } 240 | 241 | let ifValue = i / f 242 | if case .float(_, _) = ifValue { // case let .float(m, e) 243 | // XCTAssertEqual(m, 5) 244 | // XCTAssertEqual(e, -1) 245 | XCTAssertEqual(ifValue.value, -0.6666, accuracy: 0.0001) 246 | } else { 247 | XCTFail() 248 | } 249 | 250 | let idValue = i / d 251 | if case .double(_, _) = idValue { // case let .double(m, e) 252 | XCTAssertEqual(idValue.value, -0.0004, accuracy: 0.0001) 253 | } else { 254 | XCTFail() 255 | } 256 | 257 | let icValue = i / c 258 | if case .decimal(_) = icValue { // case let .decimal(d) 259 | XCTAssertEqual(icValue.value, -0.8904, accuracy: 0.0001) 260 | } else { 261 | XCTFail("\(icValue)") 262 | } 263 | 264 | let dfValue = d / f 265 | if case .double(_, _) = dfValue { // case let .double(m, e) 266 | XCTAssertEqual(dfValue.value, 1666.6666, accuracy: 0.0001) 267 | } else { 268 | XCTFail() 269 | } 270 | } 271 | 272 | func testNegate() { 273 | let i = NumericValue.integer(-1) 274 | let f = NumericValue.float(mantissa: 15, exponent: -1) 275 | let d = NumericValue.double(mantissa: 25, exponent: 2) 276 | let c = NumericValue.decimal(Decimal(string: "1.123")!) 277 | 278 | let iValue = -i 279 | if case .integer(let v) = iValue { 280 | XCTAssertEqual(v, 1) 281 | } else { 282 | XCTFail() 283 | } 284 | 285 | let fValue = -f 286 | if case .float(_, _) = fValue { // case let .float(m, e) 287 | // XCTAssertEqual(m, 5) 288 | // XCTAssertEqual(e, -1) 289 | XCTAssertEqual(fValue.value, -1.5, accuracy: 0.1) 290 | } else { 291 | XCTFail() 292 | } 293 | 294 | let dValue = -d 295 | if case .double(_, _) = dValue { // case let .double(m, e) 296 | XCTAssertEqual(dValue.value, -2500.0, accuracy: 0.1) 297 | } else { 298 | XCTFail("\(dValue)") 299 | } 300 | 301 | let cValue = -c 302 | if case .decimal(_) = cValue { // case let .decimal(d) 303 | XCTAssertEqual(cValue.value, -1.123, accuracy: 0.001) 304 | } else { 305 | XCTFail("\(cValue)") 306 | } 307 | } 308 | 309 | func testEquality() { 310 | let i = NumericValue.integer(-1) 311 | let i2 = NumericValue.integer(-1) 312 | let f = NumericValue.float(mantissa: -1, exponent: 0) 313 | let d = NumericValue.double(mantissa: -1, exponent: 0) 314 | let c = NumericValue.decimal(Decimal(string: "-1.0")!) 315 | 316 | XCTAssertTrue(i === i) 317 | XCTAssertTrue(i === i2) 318 | XCTAssertFalse(i === f) 319 | XCTAssertFalse(i === d) 320 | XCTAssertFalse(i === c) 321 | 322 | XCTAssertTrue(f === f) 323 | XCTAssertFalse(f === d) 324 | XCTAssertFalse(f === c) 325 | 326 | XCTAssertTrue(d === d) 327 | XCTAssertFalse(d === c) 328 | 329 | XCTAssertTrue(c === c) 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /docs/Extensions.md: -------------------------------------------------------------------------------- 1 | Extensions to SPARQL 1.1 2 | ======================== 3 | 4 | EXTENSION-001: Window Functions 5 | ------------- 6 | 7 | *Status*: Fully implemented 8 | *branch*: master 9 | 10 | Support for [window functions](https://github.com/w3c/sparql-12/issues/47) in `SELECT` or `HAVING` clauses. 11 | 12 | Window functions take the form: 13 | 14 | `expression OVER (PARTITION BY partition_defn ORDER BY comparators [windowframe] )` 15 | 16 | Expressions must contain a Valid window function: 17 | 18 | * `RANK()` 19 | * `DENSE_RANK()` 20 | * `ROW_NUMBER()` 21 | * `NTILE(integer)` 22 | * aggregates (without the use of `DISTINCT`) 23 | * extension window functions identified by IRI or PrefixedName (just like non-window functions) 24 | 25 | Sort `comparators` are the same as in an `ORDER BY` clause. 26 | 27 | The `windowframe` takes one of the following forms: 28 | 29 | `frametype BETWEEN UNBOUNDED AND n PRECEDING` 30 | `frametype BETWEEN UNBOUNDED AND CURRENT ROW` 31 | `frametype BETWEEN UNBOUNDED AND n FOLLOWING` 32 | `frametype BETWEEN UNBOUNDED AND n PRECEDING` 33 | `frametype BETWEEN n PRECEDING AND m PRECEDING` 34 | `frametype BETWEEN n PRECEDING AND CURRENT ROW` 35 | `frametype BETWEEN n PRECEDING AND m FOLLOWING` 36 | `frametype BETWEEN n PRECEDING AND UNBOUNDED` 37 | `frametype BETWEEN CURRENT ROW AND n FOLLOWING` 38 | `frametype BETWEEN CURRENT ROW AND n FOLLOWING` 39 | `frametype BETWEEN CURRENT ROW AND n FOLLOWING` 40 | `frametype BETWEEN CURRENT ROW AND UNBOUNDED` 41 | `frametype BETWEEN n FOLLOWING AND n FOLLOWING` 42 | `frametype BETWEEN n FOLLOWING AND UNBOUNDED` 43 | 44 | where `frametype` is either `ROWS` or `RANGE`. 45 | 46 | EXTENSION-002: SPARQL* 47 | ------------- 48 | 49 | *Status*: Parsing SPARQL* embedded triple patterns and `BIND` expressions resulting in AST using standard RDF reification is supported. Use of embedded triple patterns in `CONSTRUCT` patterns is not implemented. 50 | *branch*: sparql-star 51 | 52 | [SPARQL*](https://arxiv.org/pdf/1406.3399.pdf) syntax for expressing reification: 53 | 54 | ``` 55 | SELECT ?age ?src WHERE { 56 | ?bob foaf:name "Bob" . 57 | << ?bob foaf:age ?age >> dct:source ?src . 58 | } 59 | ``` 60 | 61 | EXTENSION-003: `GROUP_CONCAT` ordering 62 | ------------- 63 | 64 | *Status*: AST support is implemented; parsing is not implemented yet. 65 | *branch*: sparql-star 66 | 67 | Allows values to be sorted before string concatenation occurs: 68 | 69 | ``` 70 | GROUP_CONCAT(DISTINCT ?names; SEPARATOR=", ", ORDER BY ?names) 71 | ``` 72 | 73 | EXTENSION-004: `xsd:duration` support 74 | ------------- 75 | 76 | *Status*: AST support for casts and datetime functions, and init/property Term extensions implemented 77 | *branch*: sparql-12 78 | 79 | Allows SPARQL function-style casting to `xsd:time` and `xsd:duration`, and use of `ADJUST` function. 80 | 81 | -------------------------------------------------------------------------------- /examples/messy.rq: -------------------------------------------------------------------------------- 1 | prefix geo: 2 | select ?s 3 | where{ 4 | ?s geo:lat ?lat ;geo:long ?long ; 5 | FILTER(?long < -117.0) 6 | FILTER(?lat >= 31.0) 7 | FILTER(?lat <= 33.0) 8 | } ORDER BY ?s 9 | 10 | --------------------------------------------------------------------------------