summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLeonardo Sápiras2010-08-26 23:47:53 +0000
committerioguix2010-08-26 23:47:53 +0000
commitffed251517cce0de23d9c60362d0223cab0ee76e (patch)
tree6c7f5e1e5829166a83b8095e83cefa8b5126f8b6
parent0ed9c429c038307e7bc57dea7cc5bb6c59a36489 (diff)
Add feature: Browsing on Foreign Keys, in both pure xhtml and ajax
done during GSoC 2010, with mentoring and some help from ioguix.
-rw-r--r--CREDITS3
-rw-r--r--HISTORY5
-rw-r--r--TODO2
-rw-r--r--display.php255
-rw-r--r--js/display.js78
-rw-r--r--themes/default/global.css22
6 files changed, 319 insertions, 46 deletions
diff --git a/CREDITS b/CREDITS
index 6bd8f4d3..28538a6f 100644
--- a/CREDITS
+++ b/CREDITS
@@ -69,6 +69,7 @@ Contributors
- Tomasz Pala
- Ivan Zolotukhin
- Kristoffer `spq` Janke
+- Leonardo Augusto Sapiras (Improve phpPgAdmin ergonomy during the GSoC 2010, with JGuillaume 'ioguix' De Rorthais as mentor)
Third Party Libraries
@@ -80,4 +81,4 @@ Corporate Sponsors
- SpikeSource (www.spikesource.com) - Slony support
- Google Summer of Code (http://code.google.com/soc/2006/pgsql/appinfo.html?csaid=DB096D908B948D89) - phpPgAdmin Improvements
- Google Summer of Code (http://code.google.com/soc/2007/postgres/appinfo.html?csaid=E89B3D5E2DC4170A) - Full Text Search in PostgreSQL GUI Tools
-
+- Google Summer of Code (http://code.google.com/p/google-summer-of-code-2010-postgresql/we_dont_have_the_complete_link_yet) - Improve phpPgAdmin ergonomy
diff --git a/HISTORY b/HISTORY
index 6e8a03bc..a5f757a5 100644
--- a/HISTORY
+++ b/HISTORY
@@ -19,13 +19,16 @@ Features
* Add ability to create indexes concurrently
* Allow user to logicaly group their server under custom named node in the browser tree
* New theme and a theme switcher on the introduction page
+* Auto refresh Locks page
+* Auto refresh Processes page
+* Link in the bottom of the page to go to top of page
+* Browsing on Foreign Keys (When browsing a table, clicking on a FK value, jump to the PK row)
Bugs
* Fix problems with query tracking on overly long queries
* Ensure pg_dump paths are valid
* Fix problems with table names containing quotes
* Fix autocompletion support for cross-schema objects
-* Fix several problems for object names containing html charecters
Translations
* Czech (Marek Cernocky)
diff --git a/TODO b/TODO
index cc2912d7..7b6bd8d8 100644
--- a/TODO
+++ b/TODO
@@ -88,8 +88,6 @@ Tables
* Allow PK and UNIQUE and FKs during create table (Jawed)
* When adding a column or creating a table, prevent size on non-size types (eg. integer(2)). You can find these by looking at format_type in the postgresql source code.
-* When browsing a table, clicking on a FK value should jump to the
- PK row. (ioguix)
* Add WITH storage_parameter option to create table [8.2]
* Add last vacuum and analyze information from statistics tables [8.2]
* Restrict operators (from $selectOps array) to appropriate types (ie. no LIKE for int4 fields)
diff --git a/display.php b/display.php
index c16fe01b..f47c1b4f 100644
--- a/display.php
+++ b/display.php
@@ -246,6 +246,175 @@
}
}
+
+ /* build & return the FK information data structure
+ * used when deciding if a field should have a FK link or not*/
+ function &getFKInfo() {
+ global $data, $misc, $lang;
+
+ // Get the foreign key(s) information from the current table
+ $fkey_information = array('byconstr' => array(), 'byfield' => array());
+
+ if (isset($_REQUEST['table'])) {
+ $constraints = $data->getConstraintsWithFields($_REQUEST['table']);
+ if ($constraints->recordCount() > 0) {
+
+ /* build the common parts of the url for the FK */
+ $fk_return_url = "{$misc->href}&subject=table&table=". urlencode($_REQUEST['table']);
+ if (isset($_REQUEST['page'])) $fk_return_url .= "&page=" . urlencode($_REQUEST['page']);
+ if (isset($_REQUEST['query'])) $fk_return_url .= "&query=" . urlencode($_REQUEST['query']);
+ if (isset($_REQUEST['search_path'])) $fk_return_url .= "&search_path=" . urlencode($_REQUEST['search_path']);
+
+ /* yes, we double urlencode fk_return_url so parameters here don't
+ * overwrite real one when included in the final url */
+ $fkey_information['common_url'] = $misc->getHREF('schema') .'&subject=table&return_url=display.php?'
+ . urlencode($fk_return_url) .'&return_desc='. urlencode($lang['strback']);
+
+ /* build the FK constraints data structure */
+ while (!$constraints->EOF) {
+ $constr =& $constraints->fields;
+ if ($constr['contype'] == 'f') {
+
+ if (!isset($fkey_information['byconstr'][$constr['conid']])) {
+ $fkey_information['byconstr'][$constr['conid']] = array (
+ 'url_data' => 'table='. urlencode($constr['f_table']) .'&schema='. urlencode($constr['f_schema']),
+ 'fkeys' => array(),
+ 'consrc' => $constr['consrc']
+ );
+ }
+
+ $fkey_information['byconstr'][$constr['conid']]['fkeys'][$constr['p_field']] = $constr['f_field'];
+
+ if (!isset($fkey_information['byfield'][$constr['p_field']]))
+ $fkey_information['byfield'][$constr['p_field']] = array();
+
+ $fkey_information['byfield'][$constr['p_field']][] = $constr['conid'];
+ }
+ $constraints->moveNext();
+ }
+ }
+ }
+
+ return $fkey_information;
+ }
+
+ /* Print table header cells
+ * @param $sortLink must be urlencoded already
+ * */
+ function printTableHeaderCells(&$rs, $sortLink, $withOid) {
+ global $misc, $data, $conf;
+ $j = 0;
+
+ foreach ($rs->fields as $k => $v) {
+
+ if (($k == $data->id) && ( !($withOid && $conf['show_oids']) )) {
+ $j++;
+ continue;
+ }
+ $finfo = $rs->fetchField($j);
+
+ if ($sortLink === false) {
+ echo "<th class=\"data\">", $misc->printVal($finfo->name), "</th>\n";
+ }
+ else {
+ echo "<th class=\"data\"><a href=\"display.php?{$sortLink}&amp;sortkey=", ($j + 1), "&amp;sortdir=";
+ // Sort direction opposite to current direction, unless it's currently ''
+ echo ($_REQUEST['sortdir'] == 'asc' && $_REQUEST['sortkey'] == ($j + 1)) ? 'desc' : 'asc';
+ echo "&amp;strings=", urlencode($_REQUEST['strings']),
+ "&amp;page=" . urlencode($_REQUEST['page']), "\">",
+ $misc->printVal($finfo->name), "</a></th>\n";
+ }
+ $j++;
+ }
+
+ reset($rs->fields);
+ }
+
+ /* Print data-row cells */
+ function printTableRowCells(&$rs, &$fkey_information, $withOid) {
+ global $data, $misc, $conf;
+ $j = 0;
+
+ if (!isset($_REQUEST['strings'])) $_REQUEST['strings'] = 'collapsed';
+
+ foreach ($rs->fields as $k => $v) {
+ $finfo = $rs->fetchField($j++);
+
+ if (($k == $data->id) && ( !($withOid && $conf['show_oids']) )) continue;
+ elseif ($v !== null && $v == '') echo "<td>&nbsp;</td>";
+ else {
+ echo "<td style=\"white-space:nowrap;\">";
+
+ if (($v !== null) && isset($fkey_information['byfield'][$k])) {
+ foreach ($fkey_information['byfield'][$k] as $conid) {
+
+ $query_params = $fkey_information['byconstr'][$conid]['url_data'];
+
+ foreach ($fkey_information['byconstr'][$conid]['fkeys'] as $p_field => $f_field) {
+ $query_params .= '&amp;'. urlencode("fkey[{$f_field}]") .'='. urlencode($rs->fields[$p_field]);
+ }
+
+ /* $fkey_information['common_url'] is already urlencoded */
+ $query_params .= '&amp;'. $fkey_information['common_url'];
+ echo "<div style=\"display:inline-block;\">";
+ echo "<a class=\"fk fk_". htmlentities($conid) ."\" href=\"display.php?{$query_params}\">";
+ echo "<img src=\"".$misc->icon('ForeignKey')."\" style=\"vertical-align:middle;\" alt=\"[fk]\" title=\""
+ . htmlentities($fkey_information['byconstr'][$conid]['consrc'])
+ ."\" />";
+ echo "</a>";
+ echo "</div>";
+ }
+ echo $misc->printVal($v, $finfo->type, array('null' => true, 'clip' => ($_REQUEST['strings']=='collapsed'), 'class' => 'fk_value'));
+ } else {
+ echo $misc->printVal($v, $finfo->type, array('null' => true, 'clip' => ($_REQUEST['strings']=='collapsed')));
+ }
+ echo "</td>";
+ }
+ }
+ }
+
+ /* Print the FK row, used in ajax requests */
+ function doBrowseFK() {
+ global $data, $misc, $lang;
+
+ $ops = array();
+ foreach($_REQUEST['fkey'] as $x => $y) {
+ $ops[$x] = '=';
+ }
+ $query = $data->getSelectSQL($_REQUEST['table'], array(), $_REQUEST['fkey'], $ops);
+ $_REQUEST['query'] = $query;
+
+ $fkinfo =& getFKInfo();
+
+ $max_pages = 1;
+ // Retrieve page from query. $max_pages is returned by reference.
+ $rs = $data->browseQuery('SELECT', $_REQUEST['table'], $_REQUEST['query'],
+ null, null, 1, 1, $max_pages);
+
+ echo "<a href=\"\" style=\"display:table-cell;\" class=\"fk_delete\"><img alt=\"[delete]\" src=\"". $misc->icon('Delete') ."\" /></a>\n";
+ echo "<div style=\"display:table-cell;\">";
+
+ if (is_object($rs) && $rs->recordCount() > 0) {
+ /* we are browsing a referenced table here
+ * we should show OID if show_oids is true
+ * so we give true to withOid in functions bellow
+ * as 3rd paramter */
+
+ echo "<table><tr>";
+ printTableHeaderCells($rs, false, true);
+ echo "</tr>";
+ echo "<tr class=\"data1\">\n";
+ printTableRowCells($rs, $fkinfo, true);
+ echo "</tr>\n";
+ echo "</table>\n";
+ }
+ else
+ echo $lang['strnodata'];
+
+ echo "</div>";
+
+ exit;
+ }
/**
* Displays requested data
@@ -269,6 +438,16 @@
}
$misc->printTrail(isset($subject) ? $subject : 'database');
+
+ /* This code is used when browsing FK in pure-xHTML (without js) */
+ if (isset($_REQUEST['fkey'])) {
+ $ops = array();
+ foreach($_REQUEST['fkey'] as $x => $y) {
+ $ops[$x] = '=';
+ }
+ $query = $data->getSelectSQL($_REQUEST['table'], array(), $_REQUEST['fkey'], $ops);
+ $_REQUEST['query'] = $query;
+ }
if (isset($object)) {
if (isset($_REQUEST['query'])) {
@@ -314,7 +493,9 @@
isset($_REQUEST['query']) ? $_REQUEST['query'] : null,
$_REQUEST['sortkey'], $_REQUEST['sortdir'], $_REQUEST['page'],
$conf['max_rows'], $max_pages);
-
+
+ $fkey_information =& getFKInfo();
+
// Build strings for GETs
$gets = $misc->href;
if (isset($object)) $gets .= "&amp;" . urlencode($subject) . '=' . urlencode($object);
@@ -336,8 +517,9 @@
if (is_object($rs) && $rs->recordCount() > 0) {
// Show page navigation
$misc->printPages($_REQUEST['page'], $max_pages, "display.php?page=%s&amp;{$gets}&amp;{$getsort}&amp;nohistory=t&amp;strings=" . urlencode($_REQUEST['strings']));
- echo "<table>\n<tr>";
-
+
+ echo "<table id=\"data\">\n<tr>";
+
// Check that the key is actually in the result set. This can occur for select
// operations where the key fields aren't part of the select. XXX: We should
// be able to support this, somehow.
@@ -353,31 +535,11 @@
if (sizeof($key) > 0)
echo "<th colspan=\"2\" class=\"data\">{$lang['stractions']}</th>\n";
- $j = 0;
- foreach ($rs->fields as $k => $v) {
- if (isset($object) && $k == $data->id && !$conf['show_oids']) {
- $j++;
- continue;
- }
- $finfo = $rs->fetchField($j);
- // Display column headers with sorting options, unless we're PostgreSQL
- // 7.0 and it's a non-TABLE mode
- if ($type != 'TABLE') {
- echo "<th class=\"data\">", $misc->printVal($finfo->name), "</th>\n";
- }
- else {
- echo "<th class=\"data\"><a href=\"display.php?{$gets}&amp;sortkey=", ($j + 1), "&amp;sortdir=";
- // Sort direction opposite to current direction, unless it's currently ''
- echo ($_REQUEST['sortdir'] == 'asc' && $_REQUEST['sortkey'] == ($j + 1)) ? 'desc' : 'asc';
- echo "&amp;strings=", urlencode($_REQUEST['strings']),
- "&amp;page=" . urlencode($_REQUEST['page']), "\">",
- $misc->printVal($finfo->name), "</a></th>\n";
- }
- $j++;
- }
-
+ /* we show OIDs only if we are in TABLE or SELECT type browsing */
+ printTableHeaderCells($rs, $gets, isset($object));
+
echo "</tr>\n";
-
+
$i = 0;
reset($rs->fields);
while (!$rs->EOF) {
@@ -406,21 +568,15 @@
urlencode($_REQUEST['page']), "&amp;{$key_str}&amp;{$gets}&amp;{$getsort}\">{$lang['strdelete']}</a></td>\n";
}
}
- $j = 0;
- foreach ($rs->fields as $k => $v) {
- $finfo = $rs->fetchField($j++);
- if (isset($_REQUEST['table']) && $k == $data->id && !$conf['show_oids']) continue;
- elseif ($v !== null && $v == '') echo "<td>&nbsp;</td>";
- else {
- echo "<td style=\"white-space:nowrap;\">",
- $misc->printVal($v, $finfo->type, array('null' => true, 'clip' => ($_REQUEST['strings']=='collapsed'))), "</td>";
- }
- }
+
+ print printTableRowCells($rs, $fkey_information, isset($object));
+
echo "</tr>\n";
$rs->moveNext();
$i++;
}
- echo "</table>\n";
+ echo "</table>\n";
+
echo "<p>", $rs->recordCount(), " {$lang['strrows']}</p>\n";
// Show page navigation
$misc->printPages($_REQUEST['page'], $max_pages, "display.php?page=%s&amp;{$gets}&amp;{$getsort}&amp;strings=" . urlencode($_REQUEST['strings']));
@@ -450,7 +606,7 @@
// Create report
if (isset($_REQUEST['query']) && ($subject !== 'report') && $conf['show_reports'] && isset($rs) && is_object($rs) && $rs->recordCount() > 0)
echo "\t<li><a href=\"reports.php?{$misc->href}&amp;action=create&amp;report_sql=",
- urlencode($_REQUEST['query']), "&amp;paginate=", urlencode($_REQUEST['paginate']), "\">{$lang['strcreatereport']}</a></li>\n";
+ urlencode($_REQUEST['query']), "&amp;paginate=", (isset($_REQUEST['paginate'])? urlencode($_REQUEST['paginate']):'f'), "\">{$lang['strcreatereport']}</a></li>\n";
// Create view and download
if (isset($_REQUEST['query']) && isset($rs) && is_object($rs) && $rs->recordCount() > 0) {
@@ -475,10 +631,25 @@
"\">{$lang['strrefresh']}</a></li>\n";
echo "</ul>\n";
}
-
+
+
+ /* shortcuts: this function exit the script for ajax purpose */
+ if ($action == 'dobrowsefk') {
+ doBrowseFK();
+ }
+
+ $scripts = "<script src=\"libraries/js/jquery.js\" type=\"text/javascript\"></script>\n";
+ $scripts .= "<script src=\"js/display.js\" type=\"text/javascript\"></script>";
+
+ $scripts .= "<script type=\"text/javascript\">\n";
+ $scripts .= "var Display = {\n";
+ $scripts .= "errmsg: '". str_replace("'", "\'", $lang['strconnectionfail']) ."'\n";
+ $scripts .= "};\n";
+ $scripts .= "</script>\n";
+
// If a table is specified, then set the title differently
if (isset($_REQUEST['subject']) && isset($_REQUEST[$_REQUEST['subject']]))
- $misc->printHeader($lang['strtables']);
+ $misc->printHeader($lang['strtables'], $scripts);
else
$misc->printHeader($lang['strqueryresults']);
@@ -498,7 +669,7 @@
break;
case 'confdelrow':
doDelRow(true);
- break;
+ break;
default:
doBrowse();
break;
diff --git a/js/display.js b/js/display.js
new file mode 100644
index 00000000..f9180c67
--- /dev/null
+++ b/js/display.js
@@ -0,0 +1,78 @@
+$(document).ready(function() {
+
+ /* init some needed tags and values */
+
+ $('table#data').wrap('<div id="fkcontainer" class="fk" />');
+ $('#fkcontainer').append('<div id="root" />');
+
+ jQuery.ppa = {
+ root: $('#root'),
+ };
+
+ $("a.fk").live('click', function (event) {
+ /* make the cursor being a waiting cursor */
+ $('body').css('cursor','wait');
+
+ query = $.ajax({
+ type: 'GET',
+ dataType: 'html',
+ data: {action:'dobrowsefk'},
+ url: $(this).attr('href'),
+ cache: false,
+ context: $(this),
+ contentType: 'application/x-www-form-urlencoded',
+ success: function(answer) {
+ pdiv = this.closest('div.fk');
+ divclass = this.attr('class').split(' ')[1];
+
+ /* if we are clicking on a FK from the original table
+ (level 0), we are using the #root div as parent-div */
+ if (pdiv[0].id == 'fkcontainer') {
+ /* computing top position, which is the topid as well */
+ var top = this.position().top + 2 + this.height();
+ /* if the requested top position is different than
+ the previous topid position of #root, empty and position it */
+ if (top != jQuery.ppa.root.topid)
+ jQuery.ppa.root.empty()
+ .css({
+ left: (pdiv.position().left) +'px',
+ top: top + 'px'
+ })
+ /* this "topid" allows to track if we are
+ opening a FK from the same line in the original table */
+ .topid = top;
+
+ pdiv = jQuery.ppa.root;
+
+ /* Remove equal rows in the root div */
+ jQuery.ppa.root.children('.'+divclass).remove();
+ }
+ else {
+ /* Remove equal rows in the pdiv */
+ pdiv.children('div.'+divclass).remove();
+ }
+
+ /* creating the data div */
+ newdiv = $('<div class="fk '+divclass+'">').html(answer);
+
+ /* appending it to the level-1 div */
+ pdiv.append(newdiv);
+ },
+
+ error: function() {
+ this.closest('div.fk').append('<p class="errmsg">'+Display.errmsg+'</p>');
+ },
+
+ complete: function () {
+ $('body').css('cursor','auto');
+ }
+ });
+
+ return false; // do not refresh the page
+ });
+
+ $(".fk_delete").live('click', function (event) {
+ $(this).closest('div').remove();
+ return false; // do not refresh the page
+ });
+});
diff --git a/themes/default/global.css b/themes/default/global.css
index fd762a47..5fee3295 100644
--- a/themes/default/global.css
+++ b/themes/default/global.css
@@ -466,3 +466,25 @@ pre.error
border-left: 1px dotted #999;
font-size: smaller;
}
+
+div.fk {
+ background: white;
+ border:1px solid black;
+ margin-left: 20px;
+}
+
+div#fkcontainer {
+ margin: 0;
+ position: relative;
+ width: 100%;
+ background: none;
+ border:0px;
+}
+
+div#root{
+ position: absolute;
+}
+
+div.fk_value {
+ display:inline-block;
+}