diff --git a/docs/docs/models-and-databases/queries.md b/docs/docs/models-and-databases/queries.md index 78e3c71c1..696ea1bc5 100644 --- a/docs/docs/models-and-databases/queries.md +++ b/docs/docs/models-and-databases/queries.md @@ -131,7 +131,7 @@ Author.filter { q(first_name: "Bob") | q(first_name: "Alice") } Author.filter { -q(first_name: "Alice") } ``` -Marten also has the option to filter query sets using [raw SQL predicates](./raw-sql#filtering-with-raw-sql-predicates). This is useful when you want to leverage the flexibility of SQL for specific conditions, but still want Marten to handle the column selection and query building for the rest of the query. +Marten also has the option to filter query sets using [raw SQL predicates](./raw-sql#filtering-with-raw-sql-predicates). This is useful when you want to leverage the flexibility of SQL for specific conditions, but still want Marten to handle the column selection and query building for the rest of the query. To use raw SQL predicates, can specify a string containing the predicate with optional parameters to the `#filter` query set method: ```crystal Author.filter("first_name = :first_name", first_name: "John") diff --git a/docs/docs/models-and-databases/raw-sql.md b/docs/docs/models-and-databases/raw-sql.md index 912a9587f..1f0b09dc7 100644 --- a/docs/docs/models-and-databases/raw-sql.md +++ b/docs/docs/models-and-databases/raw-sql.md @@ -64,38 +64,61 @@ Finally, it should be noted that Marten does not validate the SQL queries you sp ## Filtering with raw SQL predicates -Marten also provides a feature to filter query sets using raw SQL predicates within the `#filter` method. This is useful when you need more complex filtering logic than simple field comparisons but still want to leverage Marten's query building capabilities. +Marten provides a feature to filter query sets using raw SQL predicates within the `#filter` method. This is useful when you need more complex filtering logic than simple field comparisons but still want to leverage Marten's query building capabilities. -### Positional arguments - -You can pass a raw SQL predicate fragment along with its parameters directly to the `#filter` method: +Using raw SQL predicates involves specifying a string containing the actual predicate and optional parameters to the `#filter` query set method. For example: ```crystal -Post.all.filter("published = ?", true) +Author.filter("author_id IS NOT NULL") +Author.filter("first_name = ?", "John") ``` -### Named arguments +### Specifying parameters + +You can "inject" parameters into your SQL raw predicates when using the `#filter` method. To do so you have two options: either you specify these parameters as positional arguments, or you specify them as named arguments. Positional parameters must be specified using the `?` syntax while named parameters must be specified using the `:param` format. + +For example, the following query uses positional parameters: + +```crystal +Article.filter("title = ? and created_at > ?", "Hello World!", "2022-10-30" +``` -To make your queries more readable, use named parameters: +And the following one uses named parameters: ```crystal -Post.all.filter("published = :is_published", is_published: true) +Article.filter( + "title = :title and created_at > :created_at", + title: "Hello World!", + created_at: "2022-10-30" +) ``` -### Q expression +:::caution +**Do not use string interpolations in your SQL predicates!** + +You should never use string interpolations in your raw SQL predicates as this would expose your code to SQL injection attacks (where attackers can inject and execute arbitrary SQL into your database). -For even more flexibility, you can combine raw SQL predicates with the [q expression](./queries#complex-filters-with-q-expressions) syntax within a block: +As such, never - ever - do something like that: ```crystal -Post.all.filter { q(category: "news") & q("created_at > ?", Time.local - 7.days) } +Article.filter("title = '#{title}'") +``` + +And instead, do something like that: + +```crystal +Article.filter("title = ?", title) ``` -### Advanced queries +Also, note that the parameters are left **unquoted** in the raw SQL queries: this is very important as not doing it would expose your code to SQL injection vulnerabilities as well. Parameters are quoted automatically by the underlying database backend. +::: + +### Using `q` expressions -A subquery can also be used inside the `#filter` method: +For even more flexibility, you can combine raw SQL predicates with the [`q` expression](./queries#complex-filters-with-q-expressions) syntax within a block: ```crystal -Product.all.filter("price < (SELECT AVG(price) FROM main_product)") +Post.all.filter { q(category: "news") & q("created_at > ?", Time.local - 7.days) } ``` ## Executing other SQL statements