You probably don't need query builders
Using SQL directly simplifies query construction in Rust, reducing complexity and improving readability. The article highlights SQL features and practical examples for efficient filtering, pagination, and updates.
Read original articleThe article discusses the advantages of using SQL directly over query builders when constructing database queries in Rust. It highlights a common scenario where developers need to dynamically build queries based on search filters, such as user attributes. The author critiques the complexity introduced by query builders, which often require multiple conditional statements to handle optional fields. Instead, the author advocates for using SQL's capabilities to simplify query construction. By leveraging SQL features like `IS NULL` checks and `COALESCE`, developers can create more efficient and readable queries without excessive Rust logic. The article also covers practical examples, including filtering with vectors, pagination, and updating records, demonstrating how SQL can handle these tasks more elegantly. The author concludes that relying on SQL reduces the need for complex Rust code, making the overall process cleaner and easier to manage.
- Using SQL directly can simplify query construction compared to query builders.
- SQL features like `IS NULL` and `COALESCE` can reduce the need for conditional logic in Rust.
- The article provides practical examples of filtering, pagination, and updates using SQL.
- Relying on SQL can lead to more efficient and readable code.
- The author emphasizes the importance of minimizing unnecessary complexity in database queries.
Related
I love Rust for tokenising and parsing
The author develops a Rust-based static analysis tool for SQL, sqleibniz, focusing on syntax checks and validation, utilizing macros for code efficiency, and plans to implement an LSP server.
Why I love Rust for tokenising and parsing
The author develops a Rust-based static analysis tool for SQL, named sqleibniz, focusing on syntax checks and validation for SQLite, emphasizing error message quality and employing table-driven tests.
Why I love Rust for tokenising and parsing
The author develops sqleibniz, a Rust-based static analysis tool for SQL, focusing on syntax checks and validation. Future plans include creating a Language Server Protocol server for SQL.
SQL, Homomorphisms and Constraint Satisfaction Problems
The article highlights SQL's ability to solve complex problems like Sudoku and CSPs, demonstrating efficiency in puzzles compared to Python and C, and its relationship with graph theory and homomorphisms.
Rust-Query
Lucas Holten launched rust-query, a Rust library for safe database queries without SQL, emphasizing compile-time guarantees, null safety, and type-safe migrations, currently supporting SQLite and seeking user feedback.
- Many commenters express concerns about performance issues and query planning when using dynamic SQL with optional parameters.
- There is a strong debate on the effectiveness and readability of query builders versus raw SQL, with some advocating for the former to avoid SQL injection vulnerabilities.
- Several users highlight the importance of composability in queries, suggesting that query builders can help manage complex filtering and joining scenarios.
- Some commenters share personal experiences with different tools and libraries, emphasizing the need for strong typing and safety in query construction.
- Overall, there is a call for better design in query builders to enhance usability and maintainability.
SELECT \* FROM users
WHERE id = $1
AND ($2 IS NULL OR username = $2)
AND ($3 IS NULL OR age > $3)
AND ($4 IS NULL OR age < $4)
It's worth noting that this approach has significant dangers for execution performance--it creates a significant chance that you'll get a query plan that doesn't match your actual query. See: https://use-the-index-luke.com/sql/where-clause/obfuscation/... for some related material.For each combination of parameters to search for you may want to use a different index.
But... the query plans are cached by query string lookup!
So it is imperative that your search string looks different for each query plan/index being used.
The code suggested here will pick a more or less random index (the one optimized for the parameters of the first execution) and stick with it for remaining executions, leading to bad queries for combinations of non-null that doesn't match the first query.
You could just add a comment inside the string that was different depending on what parameters are null, but that is no less complex than just generating the query.
PS: Of course there are situations where it fits, like if your strategy is to always use the same index to do the main scan and then filter away results from it based on postprocessing filters. Just make sure to understand this issue.
> My naive-self in the past used to create a fancy custom deserializer function that transformed 11,22,33,44 from a String into a Vec<i64> and that is useless work that could have easily been handled by the database.
Great, now the database has no idea what the cardinality of the IN clause is and has to generate a sub-optimal plan, because it could be 1 or it could be 10000.
The same for a lot of the other examples.
This isn’t an argument against query builders, that just seems like an argument to make your query builder easier to use and understand. I wouldn’t argue against programming languages by picking bad C++ libraries.
Shameless plug: https://github.com/baverman/sqlbind
The only alternative I know of is to make a query engine that exactly emulates the String Interpolation syntax of the host language and can detect string concatenation in the inputs.
But the problem with non-builders is always going to be GraphQL and advanced search boxes, where there are any of a couple dozen possible parameters and you either build one query that returns * for every unused clause or you have a factorial number of possible queries. If you don’t use a builder then Bobby always shows up. He even shows up sometimes with a builder.
Using raw SQL file addresses:
1. Very difficult for devs to expose SQL injection vulnerabilities because you need to use parameters.
2. Having all available filtering dimensions on a query makes it very clear what the type of filtering is for that particular query.
3. Easy debugging where you can just throw your query into an SQL client and play around with the parameters.
4. Very clear what the total query footprint of you application is (e.g. files all neatly listed in a dir).
5. Super readable and editable.
6. Code for running the SQL is pretty much: here is my query, here are my params, execute.
7. Etc?
So the amount of good you can get our of this approach is very high IMO.So an open question to anybody who is more familiar with DBs (and postgres in particular) than myself. Is there a reliable way to address the issue with this approach to querying that you all are flagging as problematic here. Because beyond the query planning issues, raw SQL files (with no building/templating) just seems to me like such a better approach to developing a db access layer.
CASE
WHEN $2 BETWEEN 0 AND 100 AND $1 > 0
THEN (($1 - 1) * $2)
ELSE
50
END
What a wonderful, maintainable language for expressing logic in /s. Perfect for my COBOL on Cogs application.The problem with SQL has never been that it's impossible to put logic in it. The problem is that it's a classic Turing Tarpit.
That said, an ORM like ActiveRecord also handles joins across related tables, and helps avoid N+1 queries, while still writing consistent access to fields.
I find myself missing ActiveRecord frequently. I know SeaORM aims to address this space, but I don't think it's there yet.
One benefit of PRQL [disclaimer: maintainer] is that it's simple to add additional logic — just add a line filtering the result:
from users
derive [full_name = name || ' ' || surname]
filter id == 42 # conditionally added only if needed
filter username == param # again, only if the param is present
take 50
Is this term Rust specific or have I slept through another change in terminology (like the day I woke up to find developers were suddenly "SWE"s)?
let mut builder = Query::select();
then you could (optionally) add clauses like so: builder.and_where(Expr::col("id").eq("A"))
it shouldn't matter if a where clause exists or not, the builder should figure that out for you.If you are going to treat your QueryBuilder as glorified StringBuilder, then of course you won't see the value of a QueryBuilder.
trance.Query[Account].Filter("foo", "=", trance.Sql("...", trance.Param("bar"))
Even for simple stuff, like each user only having permission to see parts of the database, it's essential to have a systematic way of filtering that is composable.
I'm not a fan of ORMs and I actually like SQL and yet have been using sqlalchemy expression language (the low level part of sqlalchemy) for many many years and i wouldn't really go to SQL strings.
I remember back when everything was someone's idea and others would both compliment it and improve it. Now it is like things are unchangable holy scripture. Just let `Null < 42 or Null > 42 or name = Null` all be true. What is the big deal? I can barely wrap my head around joins, the extra luggage really isn't welcome. Just have some ugly pollyfills for a decade or so. All will be fine.
Everything has it's place though. Query builders and ORMs require some effort to keep in sync with the database schema. Sometimes it's worth the effort, sometimes not.
Then you'll eventually learn why the road to hell is paved with good intentions.
Where I work we use Veracode scans regularly. Trusted 3rd party query builders are necessary to prevent them.
I mean, I get it, structures like
if(X) {
if(Y) {} else {
if(Z) {
return;
} else {}
...
will drive anybody crazy. For a query builder though, you should write something table driven where for instance you have a hash that maps query names to either functions or objects variables = { "age": where_age, "username": where_username, ... }
these could be parameterized functions, e.g. where_username = (operator, quantity) => where("username", "text", operator, quantity)
or you could have some object like {field_name: username, field_type: "text"}
and then, say loop over the get variables so, username:gt
gets broken into "username" and "gt" functions, and the where_username function gets these as arguments in the operator and quantity fields. Easy-peasy, wins at code golf if that's what you're after. Your "field" can be a subselect statement if you want to ask questions like "how pictures are in this photo gallery?"This is the kind of code that Lisp wizards wrote in the 1980s, and there's no reason you can't write it now in the many languages which contain "Lisp, the good parts."
Don't ever do this. Query builders exist to sanitize inputs in a failsafe manner. SQL has so many pitfalls that tools like sqlmap [1] exist for a reason. You will never be able to catch all encoding schemes in a regex approach to filter unsanitized input.
The examples in the blog can be exploited with a simple id set to "1 or 1=1;--" and is literally the very first web exploitation technique that is taught in highschool-level CTFs.
sqlx can mitigate a lot of problems at compile time, but sanitization is completely ignored in the post, and should at least be mentioned. If you recommend to juniors that they don't need a query builder, tell them at least why they existed in the first place.
Related
I love Rust for tokenising and parsing
The author develops a Rust-based static analysis tool for SQL, sqleibniz, focusing on syntax checks and validation, utilizing macros for code efficiency, and plans to implement an LSP server.
Why I love Rust for tokenising and parsing
The author develops a Rust-based static analysis tool for SQL, named sqleibniz, focusing on syntax checks and validation for SQLite, emphasizing error message quality and employing table-driven tests.
Why I love Rust for tokenising and parsing
The author develops sqleibniz, a Rust-based static analysis tool for SQL, focusing on syntax checks and validation. Future plans include creating a Language Server Protocol server for SQL.
SQL, Homomorphisms and Constraint Satisfaction Problems
The article highlights SQL's ability to solve complex problems like Sudoku and CSPs, demonstrating efficiency in puzzles compared to Python and C, and its relationship with graph theory and homomorphisms.
Rust-Query
Lucas Holten launched rust-query, a Rust library for safe database queries without SQL, emphasizing compile-time guarantees, null safety, and type-safe migrations, currently supporting SQLite and seeking user feedback.