-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #194 from PhilippMDoerner/moar-examples
Moar examples
- Loading branch information
Showing
4 changed files
with
214 additions
and
130 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import nimib, nimibook | ||
import norm/sqlite | ||
import ./tables | ||
import std/[with, options] | ||
|
||
nbInit(theme = useNimibook) | ||
|
||
nbText: """ | ||
# More complex queries | ||
Norm allows you to sort, limit use subqueries for complex where clauses and more. | ||
To understand how, it helps to keep in mind that norm essentially generates SQL SELECT queries after the following pattern: | ||
`SELECT <fields of model> FROM <table-name specified by model> WHERE <condition>` | ||
This means that whatever pieces of SQL come after the WHERE keyword are thing you can freely specify if need be. | ||
## Limiting the number of queried models | ||
To limit the number of queried models, simply use SQL's Limit keyword. | ||
Lets query our `Customer` table from earlier and query multiple entries, but only take the first entry. | ||
""" | ||
|
||
nbCode: | ||
var | ||
userFoo = newUser("[email protected]") | ||
alice = newCustomer(some "Alice", userFoo) | ||
bob = newCustomer(some "Bob", userFoo) | ||
with dbConn: | ||
insert userFoo | ||
insert alice | ||
insert bob | ||
|
||
var customersFoo = @[newCustomer()] | ||
dbConn.select(customersFoo, "User.email = ? LIMIT 1", "[email protected]") | ||
|
||
assert customersFoo.len() == 1 | ||
|
||
echo() | ||
|
||
nbText: """ | ||
`customersFoo` has only 1 entry, despite `alice` and `bob` both having the email address `"[email protected]"`, thanks to the LIMIT SQL keyword. | ||
## Sorting model output | ||
We can of course use ORDER BY just as we did LIMIT before: | ||
""" | ||
|
||
nbCode: | ||
var sortedCustomersFoo = @[newCustomer()] | ||
dbConn.select(sortedCustomersFoo, "User.email = ? ORDER BY name DESC", "[email protected]") | ||
|
||
assert sortedCustomersFoo[0].name.get() == "Bob" | ||
assert sortedCustomersFoo[1].name.get() == "Alice" | ||
|
||
echo() | ||
|
||
|
||
nbText: """ | ||
## Using Subqueries | ||
Similarly as to ORDER BY, you can also use subqueries within the WHERE block: | ||
""" | ||
nbCode: | ||
var subqueryCustomersFoo = @[newCustomer()] | ||
const condition = """ | ||
Customer.id IN (SELECT Cust.id FROM Customer AS Cust WHERE Cust.id % 2 == 0) | ||
""" | ||
dbConn.select(subqueryCustomersFoo, condition) | ||
assert subqueryCustomersFoo.len() == 1 | ||
assert subqueryCustomersFoo[0].id == 2 | ||
|
||
echo() | ||
nbSave |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import std/with | ||
import nimib, nimibook | ||
import norm/[model, sqlite] | ||
|
||
import ./tables | ||
|
||
nbInit(theme = useNimibook) | ||
|
||
nbText: """ | ||
### Selecting Many-To-One/One-To-Many relationships | ||
Imagine you had a Many-To-One relationship between two models, like we have with `Customer` being the many-model and `User` being the one-model, where one user can have many customers. | ||
If you have a user and wanted to query all of their customers, you couldn't do so by just making a query for the user, as that model doesn't have a "seq[Customer]" field that Norm could resolve. | ||
You could query the users for a given customer separately using the mechanisms of a general select statement. | ||
However, you can also query them separately using a convenience proc `selectOneToMany` to do all of that work for you. | ||
Just provide the "one"-side of the relationship (user), a seq of the "many-model" (seq[Customer]) to populate as before and the name of the field on the "many-model" ("user" as that's the name of field on Customer pointing to User) that points to the "one-model" (User). | ||
If your "many-model" (Customer) only has a single field pointing to the one model (User) you can even forego providing the field-name, Norm will infer it for you! | ||
""" | ||
|
||
nbCode: | ||
var | ||
userFoo = newUser("[email protected]") | ||
userBar = newUser("[email protected]") | ||
|
||
# With explicitly provided field name | ||
var customersFoo2 = @[newCustomer()] | ||
dbConn.selectOneToMany(userFoo, customersFoo2, "user") | ||
|
||
for customer in customersFoo2: | ||
echo customer[] | ||
|
||
# With inferred field name | ||
var customersFoo3 = @[newCustomer()] | ||
dbConn.selectOneToMany(userFoo, customersFoo3) | ||
|
||
for customer in customersFoo3: | ||
echo customer[] | ||
|
||
nbText: """ | ||
An additional benefit of using this `selectOneToMany` is that with it, Norm will validate whether this query is correct at compile time! | ||
In the first approach, if Customer doesn't have a field called "user" or if that field does not have any model-type that points to the "User"-table, nor an fk-pragma to any such type, then the code will throw an error with a helpful message at compile-time. | ||
In the second approach, if Customer doesn't have any field of type "User" or any other model-type that points to the same table as "User", it will also not compile while throwing a helpful error message. | ||
### Selecting Many-To-Many relationships | ||
Imagine if you had a Many-To-Many relationship between two models (e.g. Users and Groups) that is recorded on an "join-model" (e.g. UserGroup), where one user can be in many groups and a group can have many users. | ||
If you have a user and want to query all of its groups, you can do so via the general select statement mechanism. | ||
Similarly to `selectOneToMany` there is a helper proc `selectManyToMany` here for convenience. | ||
Just provide the side whose model entry you have (e.g. User or Group), a seq of the join-model (e.g. UserGroup), a seq of the entries your trying to query (e.g. seq[Group] or seq[User]), the field name on the join-model pointing to the model entry you have (e.g. "user" or "group") and the field name on the join-model pointing to the model of the entries you're trying to query (e.g. "group" or "user"). | ||
As before, if your join-model (e.g. UserGroup) only has a single field pointing to each of the two many models (e.g. User and Group), you can forego the field names and let Norm infer them for you. | ||
""" | ||
|
||
nbCode: | ||
type | ||
Group* = ref object of Model | ||
name*: string | ||
|
||
UserGroup* = ref object of Model | ||
user*: User | ||
membershipGroup*: Group | ||
|
||
func newGroup*(name = ""): Group = Group(name: name) | ||
|
||
func newUserGroup*(user = newUser(), group = newGroup()): UserGroup = UserGroup(user: user, membershipGroup: group) | ||
|
||
dbConn.createTables(newGroup()) | ||
dbConn.createTables(newUser()) | ||
dbConn.createTables(newUserGroup()) | ||
|
||
var | ||
groupFoo = newGroup("groupFoo") | ||
groupBar = newGroup("groupBar") | ||
|
||
userFooGroupFooMembership = newUserGroup(userFoo, groupFoo) | ||
userBarGroupFooMembership = newUserGroup(userBar, groupFoo) | ||
userFooGroupBarMembership = newUserGroup(userFoo, groupBar) | ||
|
||
with dbConn: | ||
insert groupFoo | ||
insert groupBar | ||
insert userFooGroupFooMembership | ||
insert userBarGroupFooMembership | ||
insert userFooGroupBarMembership | ||
|
||
# With explicitly provided fieldnames | ||
var userFooGroupMemberships: seq[UserGroup] = @[newUserGroup()] | ||
var userFooGroups: seq[Group] = @[newGroup()] | ||
dbConn.selectManyToMany(userFoo, userFooGroupMemberships, userFooGroups, "user", "membershipGroup") | ||
|
||
for group in userFooGroups: | ||
echo group[] | ||
|
||
# With inferred field names | ||
var userFooGroupMemberships2: seq[UserGroup] = @[newUserGroup()] | ||
var userFooGroups2: seq[Group] = @[newGroup()] | ||
dbConn.selectManyToMany(userFoo, userFooGroupMemberships2, userFooGroups2) | ||
|
||
for group in userFooGroups2: | ||
echo group[] | ||
|
||
nbSave |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters