2013/6/26 Albe Laurenz <laurenz.albe@xxxxxxxxxx>
Dmitriy Igrishin wrote:
>> I understand the problem now.
>> I pondered a bit over your design, and I came up with a different
>> idea how to represent prepared statements in a C++ library.
>> First, a prepared statement is identified by its name.If you want unnamed prepared statements in your library, I would
>> To make the relationship between a PreparedStatement object
>> and the PostgreSQL prepared statement unique, I suggest that
>> the prepared statement name should not be exposed to the
>> library user. It should be a private property that is
>> set in the initializer in a unique fashion (for example, it
>> could be a string representation of the memory address
>> of the object).
>> That way, there can never be a name collision. That should take
>> care of the problem.
>
>
> In fact something like was implemented in very early versions of my
> library. There are some reasons why I've redesigned the library:
>
> 1) If the user does not specify the name of the prepared statement (or
> specify it as "") it is considered as unnamed prepared statement -- a one of
> the important concepts of the frontend/backend protocol, which is a base of
> my current design.
> The unnamed prepared statements are very useful since they are deallocated
> authomatically when the backend receives the next Parse message with
> empty name.
use a different class for them since they behave quite differently.
That would also make this concern go away.
I've considered this approach also on the designing stage, but I dislike it, because
Named_prepared_statement and Unnamed_prepared_statement, derived from
Prepared_statement, will have exactly the same invariant. The name is a common
property of all prepared statements.
Since there can be only one unnamed prepared statement per
session, there should be only one such object per connection.
It should not get deallocated; maybe it could be private to the
connection, which only offers a "parseUnnamed" and "executeUnnamed"
mathod.
More precisely, there can be only one uniquely named prepared statement (named
or unnamed) per session.
Could you provide a signature of parseUnnamed and executeUnnamed please?
I don't clearly understand this approach.
That wouldn't worry me, but that's a matter of taste.
> 2) Meaningful names of the named prepared statements (as any other database
> objects) may be useful while debugging the application. Imagine the memory
> addresses (or any other surrogate names) in the Postgres logs...
You could offer a getter for the name if anybody needs it for debugging.
> Hence, the name() method should be public and name().empty() means
> unnamed prepared statement.
If you really want your users to be able to set prepared statement
names, you'd have to warn them to be careful to avoid the
problem of name collision -- you'd handle the burden to them.
That's of course also a possible way, but I thought you wanted
to avoid that.
The mentioned burden is already handled by backend which throws
duplicate_prepared_statement (42P05) error.
That seems like bad design to me.
>> I also wouldn't provide a deallocate() method. A deallocated
>> prepared statement is useless. I think that it would be more
>> logical to put that into the destructor method.
>> If somebody wants to get rid of the prepared statement
>> ahead of time, they can destroy the object.
>
>
> I've also considered this approach and there are some reasons why I don't
> implemented the prepared statement class this way:
>
> 1) There are Describe message in the protocol. Thus, any prepared statement
> can be also described this way:
> Prepared_statement* pst1 = connection->describe("name");
> Prepared_statement* pst2 = connection->describe("name"); // pst2 points to the same remote object
> Think about the pst as a pointer to the remote object (prepared statement).
> Since each statement can be described multiple times, the deleting one of them
> should not result in deallocating the prepared statement by the backend.
I wouldn't allow different objects pointing to the same prepared
statement. What is the benefit?
Shouldn't the model represent reality?
Well, then the C and C++ languages are bad designed too, because they
allow to have as many pointers to the same as the user like (needs) :-)
Really, I don't see bad design here. Describing prepared statement
multiple times will results in allocating several independent descriptors.
(As with, for example, performing two SELECTs will result in allocating
several independent results by libpq.)
As a bonus, the user can bind different data to independent prepared statements
objects stepwise (which may be important in some cases) before executing.
Of course an error during DEALLOCATE should be ignored in that case.
> 2) The best way to inform the user about errors in the modern C++ are exceptions.
> The dellocate operation (as any other query to the database) can be result in
> throwing some exception. But descructors should not throw. (If you are familiar with
> C++ well you should know about the gotchas when destructors throw.)
> So, there are deallocate() method which seems to me ok.
It's hard to conceive of a case where deallocation fails, but the
connection is fine. And if the connection is closed, the statement
will be deallocated anyway.
Why this error should be ignored? I believe that this should be decided by the user.
As a library author I don't know (and cannot know) how to react on such errors
in the end applications.
I tend to believe that such errors could also be ignored.
> Btw, by the reason 2) there are no any transaction RAII classes as in some other libraries,
> because the ROLLBACK command should be executed in the destructor and may throw.
If ROLLBACK (or anything else) throws an error, the transaction will
get rolled back anyway.
Perhaps, but, again, I don't know how the user will prefer to react. So, I prefer just
to throw and allow the user to decide.
// Dmitriy.