Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update prepared/params docs. #888

Merged
merged 1 commit into from
Aug 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 54 additions & 29 deletions include/pqxx/doc/parameters.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
Statement parameters {#parameters}
====================

When you execute a prepared statement (see @ref prepared), or a parameterised
statement (using functions like `pqxx::connection::exec_params`), you may write
special _placeholders_ in the query text. They look like `$1`, `$2`, and so
on.
In an SQL statement (including a prepared statemen), you may write special
_placeholders_ in the query text. They look like `$1`, `$2`, and so on.

If you execute the query and pass parameter values, the call will respectively
substitute the first where it finds `$1`, the second where it finds `$2`, et
cetera.
When executing the query later, you pass parameter values. The call will
respectively substitute the first parameter value where it finds `$1` in the
query, the second where it finds `$2`, _et cetera._

For example, let's say you have a transaction called `tx`. Here's how you
execute a plain statement:

```cxx
pqxx::result r = tx.exec("SELECT name FROM employee where id=101");
```

Inserting the `101` in there is awkward and even dangerous. We'll get to that
in a moment. Here's how you do it better, using parameters:

```cxx
pqxx::result r = tx.exec("SELECT name FROM employee WHERE id=$1", {101});
```

That second argument to `exec()`, the `{101}`, constructs a `pqxx::params`
object. The `exec()` call will fill this value in where the query says `$1`.

Doing this saves you work. If you don't use statement parameters, you'll need
to quote and escape your values (see `connection::quote()` and friends) as you
Expand All @@ -18,38 +33,48 @@ Or if you forget to do that, you leave yourself open to horrible
[SQL injection attacks](https://xkcd.com/327/). Trust me, I was born in a town
whose name started with an apostrophe!

Statement parameters save you this work. With these parameters you can pass
your values as-is, and they will go across the wire to the database in a safe
format.
With parameters you can pass your values as they are, and they will go across
the wire to the database in a safe format.

In some cases it may even be faster! When a parameter represents binary data
(as in the SQL `BYTEA` type), libpqxx will send it directly as binary, which is
a bit more efficient. If you insert the binary data directly in your query
text, your CPU will have some extra work to do, converting the data into a text
format, escaping it, and adding quotes.
a bit more efficient than the standard textual format in which the data
normally gets sent to the database. If you insert the binary data directly in
your query text, your CPU will have some extra work to do, converting the data
into a text format, escaping it, and adding quotes; and the data will take up
more bytes, which take time to transmit.


Dynamic parameter lists
-----------------------
Multiple parameters
-------------------

In rare cases you may just not know how many parameters you'll pass into your
statement when you call it.
The `pqxx::params` class is quite fleixble. It can contain any number of
parameter values, of many different types.

For these situations, have a look at `params`. It lets you compose your
parameters list on the fly, even add whole ranges of parameters at a time.
You can pass them in while constructing the `params` object:

You can pass a `params` into your statement as a normal parameter. It will
fill in all the parameter values it contains into that position of the
statement's overall parameter list.
```cxx
pqxx::params{23, "acceptance", 3.14159}
```

Or you can add them one by one:

So if you call your statement passing a regular parameter `a`, a
`params` containing just a parameter `b`, and another regular parameter `c`,
then your call will pass parameters `a`, `b`, and `c`. Or if the params object
is empty, it will pass just `a` and `c`. If the params object contains `x` and
`y`, your call will pass `a, x, y, c`.
```cxx
pqxx::params p;
p.append(23);
p.append("acceptance");
p.append(3.14159);
```

You can also combine the two, passing some values int the constructor and
adding the rest later. You can even insert a `params` into a `params`:

```cxx
pqxx::params p{23};
p.append(params{"acceptance", 3.14159});
```

You can mix static and dynamic parameters freely. Don't go overboard though:
complexity is where bugs happen!
Each of these examples will produce the same list of parameters.


Generating placeholders
Expand Down
44 changes: 23 additions & 21 deletions include/pqxx/doc/prepared-statement.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,20 @@ as many times as you like, typically with varying parameters. It's a lot like
a function that you can define ad hoc, within the scope of one connection.

If you have an SQL statement that you're going to execute many times in
quick succession, it may be more efficient to prepare it once and reuse it.
This saves the database backend the effort of parsing complex SQL and
figuring out an efficient execution plan. Another nice side effect is that
you don't need to worry about escaping parameters. Some corporate coding
standards require all SQL parameters to be passed in this way, to reduce the
risk of programmer mistakes leaving room for SQL injections.
quick succession, it _may_ (but see below!) be more efficient to prepare it
once and reuse it. This saves the database backend the effort of parsing the
SQL and figuring out an efficient execution plan.


Preparing a statement
---------------------

You create a prepared statement by preparing it on the connection (using the
`pqxx::connection::prepare` functions), passing an identifier and its SQL text.
`pqxx::connection::prepare` functions), passing an identifying name for the
statement, and its SQL text.

The identifier is the name by which the prepared statement will be known; it
should consist of ASCII letters, digits, and underscores only, and start with
an ASCII letter. The name is case-sensitive.
The statement's name should consist of ASCII letters, digits, and underscores
only, and start with an ASCII letter. The name is case-sensitive.

```cxx
void prepare_my_statement(pqxx::connection &cx)
Expand All @@ -34,11 +31,10 @@ an ASCII letter. The name is case-sensitive.
```

Once you've done this, you'll be able to call `my_statement` from any
transaction you execute on the same connection. For this, use the
`pqxx::transaction_base::exec()` functions that take a `pqxx::prepped` object
instead of an SQL statement string. The `pqxx::prepped` type is just a wrapper
that tells the library "this is not SQL text, it's the name of a prepared
statement."
transaction you execute on the same connection. For this, call
`pqxx::transaction_base::exec()` and pass a `pqxx::prepped` object instead of
an SQL statement string. The `pqxx::prepped` type is just a wrapper that tells
the library "this is not SQL text, it's the name of a prepared statement."

```cxx
pqxx::result execute_my_statement(pqxx::transaction_base &t)
Expand All @@ -51,9 +47,10 @@ statement."
Parameters
----------

Did I mention that prepared statements can have parameters? The query text
can contain `$1`, `$2` etc. as placeholders for parameter values that you
will provide when you invoke the prepared satement.
You can pass parameters to a prepared statemet, just like you can with a
regular statement. The query text can contain `$1`, `$2` etc. as placeholders
for parameter values that you will provide when you invoke the prepared
satement.

See @ref parameters for more about this. And here's a simple example of
preparing a statement and invoking it with parameters:
Expand All @@ -75,9 +72,9 @@ This example looks up the prepared statement "find," passes `name` and

```cxx
pqxx::result execute_find(
pqxx::transaction_base &t, std::string name, int min_salary)
pqxx::transaction_base &tx, std::string name, int min_salary)
{
return t.exec_prepared("find", name, min_salary);
return tx.exec(pqxx::prepped{"find"}, name, min_salary);
}
```

Expand All @@ -93,7 +90,7 @@ statement can be redefined at any time, without un-preparing it first.
Performance note
----------------

Don't assume that using prepared statements will speed up your application.
Don't _assume_ that using prepared statements will speed up your application.
There are cases where prepared statements are actually slower than plain SQL.

The reason is that the backend can often produce a better execution plan when
Expand All @@ -108,6 +105,11 @@ find matching email addresses first and then see which of their owners are
"inactive." A prepared statement must be planned to fit either case, but a
direct query will be optimised based on table statistics, partial indexes, etc.

So, as with any optimisation... measure where your real performance problems
are before you start making changes, and then afterwards, measure whether your
changes actually helped. Don't complicate your code unless it solves a real
problem. Knuth's Law applies.


Zero bytes
----------
Expand Down
Loading