Filtering in Swift-Kuery-ORM
By Enrique Lacal
Created on 2018-06-08
Introduction
In Kitura 2.4 we have made enhancements to Codable Query Parameters to support extra filtering. This includes the ability to filter using operators such as greater than, inclusive ranges and pagination through Codable Routing. We then took a further step and incorporated these capabilities into Swift-Kuery-ORM in order to filter results from database queries.
Setting Up Swift-Kuery-ORM
In your Kitura application, follow the README to set up Swift-Kuery-ORM using PostgreSQL, MySQL or SQLite.
-
Create a Student type that implements Model:
struct Student: Model { let name: String let age: Int let grade: Double }
-
Create the table in the database:
do { try Student.createTableSync() } catch let error { print("Error:", error) }
-
Save a list of students:
let students = [Student(name: "Mark", age: 26, grade: 78.5), Student(name: "David", age: 20, grade: 65.3), Student(name: "Alex", age: 23, grade: 82.7), Student(name: "Mark", age: 24, grade: 94.6)]
for student in students { student.save { student, error in if let error = error { print("Error:", error) } } }
4. Find a list of students and print them:
```swift
Student.findAll { students, error in
if let error = error {
print("Error:", error)
}
if let students = students {
students.forEach { print($0) }
Output:
Student(name: "Mark", age: 26, grade: 78)
Student(name: "David", age: 20, grade: 65)
Student(name: "Alex", age: 23, grade: 82)
Student(name: "Mark", age: 24, grade: 94)
This would translate in Raw SQL to:
SELECT * FROM Students;
Using Query Parameters with Swift-Kuery-ORM
The following types have been added to Kitura 2.4 to enhance filtering:
- GreaterThan & LowerThan
- GreaterThanOrEqual & LowerThanOrEqual
- InclusiveRange & ExclusiveRange
- Pagination
- Ordering
Let’s suppose we want to filter over the name of the Student, the age being greater than a value and the grade being inside a range.
-
Let’s define our Query type that implements QueryParams:
struct Query: QueryParams { let name: String let age: GreaterThan<Int>; let grade: InclusiveRange<Double>; }
-
Create an instance of Query:
let query = Query(name: "Mark", age: GreaterThan(value: 23), grade: InclusiveRange(start: 76, end: 88))
- Find the students matching the query and print them:
Student.findAll(matching: query) { students, error in
if let error = error {
print("Error:", error)
}
if let students = students {
students.forEach { print($0) }
}
}
Output:
Student(name: "Mark", age: 26, grade: 78)
This would translate in Raw SQL to:
SELECT * FROM Students WHERE ((Students.name = "Mark") AND (Students.age > 23)) AND ((Students.grade >= 76) AND (Students.grade <= 88));
The URL would look like:
?name=Mark&age=23&grade=76,88
Now, we want only the first 3 values and they have to be ordered descending by grade:
-
Let’s define our MyQuery type that implements QueryParams:
struct MyQuery: QueryParams { let pagination: Pagination let order: Ordering }
-
Create an instance of MyQuery:
let myQuery = MyQuery(pagination: Pagination(start: 0, size: 3), order: Ordering(by: .desc("grade")))
-
Find the students matching the query and print them:
Student.findAll(matching: myQuery) { students, error in if let error = error { print("Error:", error) }
if let students = students { students.forEach { print($0) } } }
Output:
```swift
Student(name: "Mark", age: 24, grade: 94)
Student(name: "Alex", age: 23, grade: 82)
Student(name: "Mark", age: 26, grade: 78)
This would translate in Raw SQL to :
SELECT * FROM Students ORDER BY Students.grade DESC LIMIT 3 OFFSET 0;
The URL would look like:
?pagination=0,3&order=desc(grade)
Working with Codable Routing in Kitura
Swift-Kuery-ORM really shines when used with Kitura, in particular because the ORM API has been aligned with Kitura’s Codable Routing APIs, allowing the completion handlers from the Codable Routes to be passed directly into the ORM calls. This means that no additional code needs to be written.
- Create a handler that retrieves the students and register it with the Router for GET requests on
/students
:
func getStudents(completion: @escaping([Student]?, RequestError?) -> Void) -> Void {
Student.findAll(completion)
}
router.get("/students", handler: getStudents)
This has implemented the following URI: GET: /students
.
- Create a handler that saves the students, and register it with the Router for POST requests on
/students
:
func saveStudents(student: Student, completion: @escaping (Student?, RequestError?) -> Void) -> Void {
student.save(completion)
}
router.post("/students", handler: saveStudents)
This has implemented the following URI: POST: /students
.
- Now let’s add filtering to the GET route by updating the getStudents handler to also accept your type that implements QueryParams as an additional parameter: Note: This is using the Query struct from earlier:
func getStudents(query: Query?, completion: @escaping([Student]?, RequestError?) -> Void) -> Void {
Student.findAll(matching: query, completion)
}
This now implements a completely type-safe implementation of both the data handling, and the URL encoded query parameters for the following URI:
GET: /users?name=<String>&age=<Int>&grade=<Int>,<Int>
Note: that the type Query is set to optional. This means that the following URI is also supported:
GET: /users
If you want to include support for optional (non-required) query parameters, you just need to mark them as optional in your declared Swift type, eg: InclusiveRange?
We have increased the capabilities of Swift-Kuery-ORM and carefully aligned it with Kitura to make persistence as easy as possible whilst maintaining type safety.
Future
The next features we are looking to integrate in Swift-Kuery-ORM, we believe will truly enhance its usage and provide essential functionalities. The following are just some of them:
- Migrations – the ability to apply incremental and reversible schema changes to the database.
- Relations between models – such as nested Models:
struct School: Model {
let name: String
}
struct Student: Model {
let name: String
let age: Int
let grade: Double
let school: School
}
- Support for NoSQL Databases such as Redis, MongoDB and CouchDB.
- Control over data types and sizes