|
16 | 16 | Otter is a pure Swift SQL compiler that allow developers to write plain compile time safe SQL. |
17 | 17 |
|
18 | 18 | - [Installation](#installation) |
| 19 | +- [Macros](#or-use-the-swift-macro) |
19 | 20 | - [Queries](#queries) |
20 | 21 | - [Types](#types) |
21 | 22 | - [Operators](#operators) |
@@ -103,6 +104,14 @@ that won't scale well beyond a fairly simple schema and a handfull of queries. |
103 | 104 | # Installation |
104 | 105 | Otter supports Swift Package Manager. To install add the following to your `Package.swift` file. |
105 | 106 |
|
| 107 | +> [!TIP] |
| 108 | +> If don't want to read any of the README, here are some quick tips: |
| 109 | +> * Use singular table names, it is the SQL standard. |
| 110 | +> * Orgranize queries in files by usage, not by table. |
| 111 | +> * Use `SELECT table.*` to embed the table structs within the results |
| 112 | +> * Inject queries and avoid the repository pattern |
| 113 | +> * Let SQL answer the questions about your data. Many queries are perfectly fine |
| 114 | +
|
106 | 115 | ```swift |
107 | 116 | let package = Package( |
108 | 117 | [...] |
@@ -140,7 +149,7 @@ feather gen |
140 | 149 |
|
141 | 150 | This will compile and check all migrations and queries, then generate all Swift required to talk to the database. |
142 | 151 |
|
143 | | -#### Adding a New Migration |
| 152 | +### Adding a New Migration |
144 | 153 | When a new migration is needed, you can simply add a new file with a number 1 higher than the previous. To automatically do this the cli tool can do it for you by running |
145 | 154 | ``` |
146 | 155 | feather migrate add |
@@ -173,22 +182,51 @@ All queries will be stored in the `/Queries` directory. More than one query can |
173 | 182 | feather queries add --name <some-name> |
174 | 183 | ``` |
175 | 184 |
|
176 | | -> [!TIP] |
177 | | -> Organize queries by usage, not by table. This will become more useful later on when we talk about dependency injection. |
178 | | -
|
179 | | -Open the file that was created in `/Queries`, it should be blank. Individual queries can be defined using the `DEFINE` keyword. At the moment queries can only have one statement. |
| 185 | +Open the file that was created in `/Queries`, it should be blank. Individual queries can be defined using the the following format. At the moment a single query can only have one statement. |
180 | 186 | ```sql |
181 | 187 | fetchUsers: |
182 | 188 | SELECT * FROM user; |
| 189 | + |
| 190 | +-- Or optionally supply either an input or output name |
| 191 | +fetchUsers(input: InputName, output: OutputName): |
| 192 | +SELECT * FROM user; |
183 | 193 | ``` |
184 | 194 |
|
185 | | -If the queries file was named `User.sql` this would be accessible via |
| 195 | +> [!TIP] |
| 196 | +> Organize queries by usage, not by table. |
| 197 | +> This will allow queries to be injected together |
| 198 | +
|
| 199 | +Each queries file will get it's own `Queries` types generated. To allow the queries defined in a file to be |
| 200 | +passed around and injected together. For example, if we have a `Library.sql` the following types will be generated: |
186 | 201 | ```swift |
187 | | -let query: any FetchUsersQuery = database.userQueries.fetchUsers |
188 | | -let users: [User] = try await query.execute() |
| 202 | +// Protocol that defines all queries in the file |
| 203 | +let queries: any LibraryQueriesType = database.libraryQueries |
| 204 | + |
| 205 | +// Concrete implementaion |
| 206 | +let concreteType: LibraryQueriesImpl |
| 207 | + |
| 208 | +// NO-OP version, can be injected into unit tests/previews |
| 209 | +let noopType: LibraryQueriesNoop |
| 210 | +``` |
| 211 | + |
| 212 | +For the `Noop` queries, we can override any query optionally. Each query be default will return `nil` or an empty `[]`. To override a query you can set it in the initializer. |
| 213 | +```swift |
| 214 | +LibraryQueriesNoop(getLibrary: Queries.Just([...])) |
189 | 215 | ``` |
190 | 216 |
|
191 | 217 | ### Input and Output Types |
| 218 | +Otter will, if needed, generate types for the inputs and outputs. If a type is a single primitive it will be mapped to the Swift equivalent. |
| 219 | +```sql |
| 220 | +-- Will return the User struct |
| 221 | +fetchUsers: |
| 222 | +SELECT * FROM user; |
| 223 | + |
| 224 | +-- Will generate a type for the id and name |
| 225 | +fetchUserIdAndNames: |
| 226 | +SELECT id, name FROM user; |
| 227 | +``` |
| 228 | + |
| 229 | +#### Embedding Table Structs |
192 | 230 | In the example above, since we selected all columns from a single table the query will return the `User` struct that was generated for the table. If additional columns are selected a new structure will be generated to match the selected columns. In the following example we will join in the `post` table to get a users post count. |
193 | 231 | ```sql |
194 | 232 | fetchUsers: |
@@ -319,3 +357,10 @@ class ViewModel { |
319 | 357 | let query: any LatestExpensesQuery |
320 | 358 | } |
321 | 359 | ``` |
| 360 | + |
| 361 | +## Upcoming Features |
| 362 | +* Support for multiple statements in a single query |
| 363 | +* Kotlin library/generation |
| 364 | + |
| 365 | +## Contributions |
| 366 | +Contributions are welcome and encouraged! Feel free to make a PR or open an issue. If the change is large please open an issue first to make sure the change is desired. |
0 commit comments