Skip to content

Commit

Permalink
Improve documentation related to raw SQL predicates
Browse files Browse the repository at this point in the history
  • Loading branch information
ellmetha committed Jun 22, 2024
1 parent 91c06f7 commit 4ce5b85
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 15 deletions.
2 changes: 1 addition & 1 deletion docs/docs/models-and-databases/queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
51 changes: 37 additions & 14 deletions docs/docs/models-and-databases/raw-sql.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 4ce5b85

Please sign in to comment.