Hi all,
Recently I wrote a small patch for frmQuery.cpp in order to
auto-generate a "SET search_path TO ..." statement when opening the
query editor with a schema-related object selected in the object
browser, e.g. a table, function or sequence (or their respective
collections).
The reason being that in our company, after migrating from MSSQL Server
to pg, we use a single database for many of our customers, with one
schema per customer and identically named objects within those schemas.
E.g. cust_schema_01.table_x, cust_schema_02.table_x, cust_schema_03.table_x.
For maintenance and debugging, we connect to the database using the
postgres superuser account in pgAdmin3. Now in order to access table_x
within a certain customer's schema in the query editor, we always have
to prepend the schema name to the table name or issue a "SET search_path
TO cust_schema_nn,public" statement.
This is rather tedious, so I came up with a patch for pgAdmin3 that
tries to intelligently generate a search_path depending on the currently
selected object in the object browser as well as depending on the
existing search_path configured for the current database connection.
That way, we can easily open query editors under different schemas
without bothering about the search_path ourselves.
This is what my code does when opening a new query editor window:
- Check whether the currently selected object in the object browser is
of type pgSchema, pgSchemaObject or pgSchemaObjCollection or one of
their descendants which (if I'm not mistaken) means it does have a
schema associated with it.
- If so, it checks whether the schema belonging to this object is
already contained in the user's search_path (case-sensitively and
considering the $user placeholder).
- If the schema isn't already in the user's search_path, the code
generates a "SET search_path TO
<selected_schema>,<existing_search_path>" statement and has it written
into the newly opened query editor window.
- After that, it places the cursor to the end of the sql text so the
user can begin typing right away.
Tested on Windows XP Pro SP3 and Windows7 Pro only - I didn't bother to
create a build environment on my Linux box yet.
In case you deem this patch useful, find the diff output based on the
1.16.1 release source code attached below. There may occur usability
problems in combination with the existing "sticky SQL" option though. We
don't use the "sticky SQL" feature in our environment, so for now I
didn't spend too much thought on it.
Beware also that though being a developer, I'm really inexperienced in
C/C++ and completely new to wxWidgets and to the inner workings of
pgAdmin, so there may well be room for improvement in my code. Having
said that, I'd be willing to dig further into the pgAdmin3 code in order
to make this a configurable option, integrate it better into the
existing code etc. if need be.
Why not just do something like:
if (obj->GetSchema())sql = wxT("SET search_path TO ") + obj->GetSchema()->GetName();
Shouldn't you just check that it's not at the front of the search path? Otherwise, if it's further back then queries might still be directed to a different check.
There may occur usability
problems in combination with the existing "sticky SQL" option though. We
don't use the "sticky SQL" feature in our environment, so for now I
didn't spend too much thought on it.
It's essential the patch works with that, if it's to have any hope of being committed.
I definitely think it needs to be a configurable option - though, I wonder how it would work alongside Sticky SQL. That just copies the SQL from the SQL pane into the query tool - but, that may have schemas in it. If the search path is set, we almost certainly wouldn't want that (related to that, are the various "XXXX Script" options on each object, which have a similar issue)
Hi,1. Unless I'm missing something here, obj->GetSchema() will never be non-zero because obj is of type pgObject and pgObject's implementation of GetSchema() always returns zero. Thus, obj has to be casted to a different type before we can call GetSchema(). And since I couldn't find a common ancestor implementing GetSchema() for all object types, I resorted to the dynamic_cast approach. Come to think of it, might it be worthwile to move this logic into pgObject's GetSchema() implementation?
Why not just do something like:
if (obj->GetSchema())sql = wxT("SET search_path TO ") + obj->GetSchema()->GetName();
2. Setting the search_path just to the schema in question will cause any reference to an object contained in the public or $user schema (which are comprising the search_path by default) to fail. That was my main concern. Also, a user's search_path may be configured to contain other schemas that are needed for application-specific queries. So in order not to break expected behaviour, my goal was to preserve the original search_path and just complement it.
Good point. But just comparing the first element could lead to results like [public,foobar,public] - call me a pedant, but I wouldn't like that :-)
Shouldn't you just check that it's not at the front of the search path? Otherwise, if it's further back then queries might still be directed to a different check.
How about something like: if schemaName is not contained in search_path then prepend it; else if schemaName is already contained in search_path, but not at the front, then move it to the front. Or, in other words: if schemaName is contained in search_path, then remove it; afterwards, always prepend schemaName to search_path.
Thought so... there are 5 options I can think of right now:
I definitely think it needs to be a configurable option - though, I wonder how it would work alongside Sticky SQL. That just copies the SQL from the SQL pane into the query tool - but, that may have schemas in it. If the search path is set, we almost certainly wouldn't want that (related to that, are the various "XXXX Script" options on each object, which have a similar issue)
1. Make Sticky SQL and auto-searchpath mutually exclusive in the options dialog.
2. Leave the choice up to the user; if the user activates both options at the same time, then be it. The user can still manually delete those parts she doesn't want.
2a. When generating the "SET search_path" statement, make it a comment (prepend it with "-- ") if Sticky SQL is activated at the same time.
2b. Make the Sticky SQL part a comment (wrap it in /* */) if auto-searchpath is enabled at the same time.
3. Make 2 & 2a & 2b configurable according to the user's personal preference :-)
As for the "XXXX script" feature, that's taking a different route in the code, so currently it doesn't collide with this search_path thing anyway.
1. Unless I'm missing something here, obj->GetSchema() will never be non-zero because obj is of type pgObject and pgObject's implementation of GetSchema() always returns zero. Thus, obj has to be casted to a different type before we can call GetSchema(). And since I couldn't find a common ancestor implementing GetSchema() for all object types, I resorted to the dynamic_cast approach. Come to think of it, might it be worthwile to move this logic into pgObject's GetSchema() implementation?Why not just do something like:
if (obj->GetSchema())sql = wxT("SET search_path TO ") + obj->GetSchema()->GetName();
You're missing something - specifically that GetSchema is declared as a virtual function. That's done so you can do exactly what I'm proposing.
2. Leave the choice up to the user; if the user activates both options at the same time, then be it. The user can still manually delete those parts she doesn't want.
Hmm. That's a possibility I guess. I'm not sure what the implications may be without playing with it though.
As for the "XXXX script" feature, that's taking a different route in the code, so currently it doesn't collide with this search_path thing anyway.
Is it? Doesn't CREATE Script get it's contents from a GetSQL() call in most cases?
I wonder if we're actually looking at it the wrong way, and what we really should consider is allowing the user to define a "template" block of SQL that's always added to any new SQL Query windows. That block could include placeholders that are replaced with context-specific values, or GUC variable values, e.g, the user could specify a template of:
SET search_path TO '%%SCHEMA%%, %%GUC:search_path%%'
Which would replace %%SCHEMA%% with the context-specific schema, and %%GUC:search_path%% with the current value of the search_path GUC.
The nice thing about doing it this way is that it can be used for a lot of different purposes - you can solve your problem, another user might have a default of "BEGIN;" to ensure they always run in an explicit transaction block etc, but perhaps more importantly, it saves us having to worry about what Sticky SQL or XXX Script features do, as it becomes an issue for the user to ensure their templates will work correctly in their environment.
That'd be a nice enhancement indeed. But using your example with the search_path, we'd still have the inconvenience of a redundant [public,myschema,public] path resulting from the template. Which OTOH is just a matter of taste (or is it?).I wonder if we're actually looking at it the wrong way, and what we really should consider is allowing the user to define a "template" block of SQL that's always added to any new SQL Query windows. That block could include placeholders that are replaced with context-specific values, or GUC variable values, e.g, the user could specify a template of:
SET search_path TO '%%SCHEMA%%, %%GUC:search_path%%'
Which would replace %%SCHEMA%% with the context-specific schema, and %%GUC:search_path%% with the current value of the search_path GUC.
The nice thing about doing it this way is that it can be used for a lot of different purposes - you can solve your problem, another user might have a default of "BEGIN;" to ensure they always run in an explicit transaction block etc, but perhaps more importantly, it saves us having to worry about what Sticky SQL or XXX Script features do, as it becomes an issue for the user to ensure their templates will work correctly in their environment.
BTW, after adapting my code for re-ordering the search_path like we discussed and making it a configurable option (which was really easy, actually), I tested another unrelated idea: executing a query in the query editor repeatedly in user-definable intervals. I intended this primarily for monitoring tasks (pgAdmin's "server status" feature is great, but sometimes you just have more specific demands) or for watching logging tables being populated by an external application. This comes with a few UI-related problems though... all input fields should be made read-only while the timer is running, and the query editor for some reason is always brought to the foreground everytime the query has completed, which can be annoying. Also, certain menu items in the query editor should be disabled when the timer is running but the statement is not currently executed.
If this feature might be of broader interest, I'd be willing to look into some of these issues.
Oh, and when testing this, I found a small bug: open query editor, click into the SQL notepad, then click on the GQB tab... pgAdmin will crash. I haven't traced this down yet, just noticed it's reproduceable.
Hey,
Now i've tested it again and of course you were right, just my testing
> You're missing something - specifically that GetSchema is declared as
> a virtual function. That's done so you can do exactly what I'm proposing.
was flawed. Probably I tested by selecting a schema and opening the
query window, not taking into account that your example code only works
below the schema level of course.
So I've boiled it down to this now:
wxString schemaName;
if(obj->GetTypeName() == wxT("Schema"))
schemaName = obj->GetName();
else if (obj->GetSchema())
schemaName = obj->GetSchema()->GetName();
if(!schemaName.IsEmpty())
{
// the works...
}
Much better. Thanks for your input.
Florian
Hi,We have checked the bug and below is our finding.As we are getting the pane information from the manager and pane could not be found in the manager so at that time wxAuiPaneInfo structure is not valid and when we call Update() then it was crashing. So before calling to Update() we put check for validating the wxAuxPaneInfo structure as below.wxAuiPaneInfo outputPaneInfo = manager.GetPane(wxT("outputPane")).Show(false);
wxAuiPaneInfo scratchPad = manager.GetPane(wxT("scratchPad")).Show(false);
viewMenu->Enable(MNU_OUTPUTPANE, false);
viewMenu->Enable(MNU_SCRATCHPAD, false);
if (outputPaneInfo.IsOk() == true && scratchPad.IsOk() == true)
manager.Update();
Please find the patch along with this email and let me know.