//////////////////////////////////////////////////////////////////////////
//
// pgAdmin III - PostgreSQL Tools
//
// Copyright (C) 2002 - 2016, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// pgTable.cpp - Table class
//
//////////////////////////////////////////////////////////////////////////

#include "pgAdmin3.h"


#include "utils/misc.h"
#include "frm/frmHint.h"
#include "frm/frmMain.h"
#include "frm/frmMaintenance.h"
#include "schema/pgTable.h"
#include "schema/pgColumn.h"
#include "schema/pgIndexConstraint.h"
#include "schema/pgForeignKey.h"
#include "schema/pgCheck.h"
#include "utils/sysSettings.h"
#include "utils/pgfeatures.h"
#include "schema/pgRule.h"
#include "schema/pgTrigger.h"
#include "schema/pgConstraints.h"
#include "schema/gpPartition.h"
#include "schema/pgPartition.h"
#include <wx/regex.h>

// App headers

pgTable::pgTable(pgSchema *newSchema, const wxString &newName)
	: pgSchemaObject(newSchema, tableFactory, newName)
{
	Init();
}

pgTable::pgTable(pgSchema *newSchema, pgaFactory &newFactory, const wxString &newName)
	: pgSchemaObject(newSchema, newFactory, newName)
{
	Init();
}

pgTable::~pgTable()
{
}


void pgTable::Init()
{
	rows = 0;
	estimatedRows = 0.0;

	hasToastTable = false;
	autovacuum_enabled = 0;
	toast_autovacuum_enabled = 0;

	isPartitioned = false;
	hasOids = false;
	unlogged = false;
	hasSubclass = false;
	rowsCounted = false;
	isReplicated = false;
	showExtendedStatistics = false;
	distributionIsRandom = false;

	inheritedTableCount = 0;
	triggerCount = 0;

	tablespaceOid = 0;
	ofTypeOid = 0;
}


wxString pgTable::GetTranslatedMessage(int kindOfMessage) const
{
	wxString message = wxEmptyString;

	switch (kindOfMessage)
	{
		case RETRIEVINGDETAILS:
			message = _("Retrieving details on table");
			message += wxT(" ") + GetName();
			break;
		case REFRESHINGDETAILS:
			message = _("Refreshing table");
			message += wxT(" ") + GetName();
			break;
		case DROPINCLUDINGDEPS:
			message = wxString::Format(_("Are you sure you wish to drop table \"%s\" including all objects that depend on it?"),
			                           GetFullIdentifier().c_str());
			break;
		case DROPEXCLUDINGDEPS:
			message = wxString::Format(_("Are you sure you wish to drop table \"%s\"?"),
			                           GetFullIdentifier().c_str());
			break;
		case DROPCASCADETITLE:
			message = _("Drop table cascaded?");
			break;
		case DROPTITLE:
			message = _("Drop table?");
			break;
		case PROPERTIESREPORT:
			message = _("Table properties report");
			message += wxT(" - ") + GetName();
			break;
		case PROPERTIES:
			message = _("Table properties");
			break;
		case DDLREPORT:
			message = _("Table DDL report");
			message += wxT(" - ") + GetName();
			break;
		case DDL:
			message = _("Table DDL");
			break;
		case DATADICTIONNARYREPORT:
			message = _("Table Data dictionary report");
			message += wxT(" - ") + GetName();
			break;
		case STATISTICSREPORT:
			message = _("Table statistics report");
			message += wxT(" - ") + GetName();
			break;
		case OBJSTATISTICS:
			message = _("Table statistics");
			break;
		case DEPENDENCIESREPORT:
			message = _("Table dependencies report");
			message += wxT(" - ") + GetName();
			break;
		case DEPENDENCIES:
			message = _("Table dependencies");
			break;
		case DEPENDENTSREPORT:
			message = _("Table dependents report");
			message += wxT(" - ") + GetName();
			break;
		case DEPENDENTS:
			message = _("Table dependents");
			break;
		case BACKUPTITLE:
			message = wxString::Format(_("Backup table \"%s\""),
			                           GetFullIdentifier().c_str());
			break;
		case RESTORETITLE:
			message = wxString::Format(_("Restore table \"%s\""),
			                           GetFullIdentifier().c_str());
			break;
	}

	return message;
}


int pgTable::GetIconId()
{
	if (isReplicated)
		return tableFactory.GetReplicatedIconId();
	else 
		if (!GetPartKeyDef().IsEmpty()||GetIsPartitioned()) return tableFactory.GetPartitionsIconId();
	    else
		return tableFactory.GetIconId();
}


wxMenu *pgTable::GetNewMenu()
{
	wxMenu *menu = pgObject::GetNewMenu();
	if (schema->GetCreatePrivilege())
	{
		columnFactory.AppendMenu(menu);
		if (GetPrimaryKey().IsEmpty())      // Will not notice if pk has been added after last refresh
			primaryKeyFactory.AppendMenu(menu);
		foreignKeyFactory.AppendMenu(menu);
		excludeFactory.AppendMenu(menu);
		uniqueFactory.AppendMenu(menu);
		checkFactory.AppendMenu(menu);
		indexFactory.AppendMenu(menu);
		ruleFactory.AppendMenu(menu);
		triggerFactory.AppendMenu(menu);

		/*
		 * TEMPORARY:  Disable adding new partitions until that code is working right.
		 *
		if (GetConnection() != 0 && GetConnection()->GetIsGreenplum() && GetIsPartitioned())
		    partitionFactory.AppendMenu(menu);
		 */
	}
	return menu;
}


int pgTable::GetReplicationStatus(ctlTree *browser, wxString *clusterName, long *setId)
{
	wxArrayString clusters = GetDatabase()->GetSlonyClusters(browser);

	bool isSubscribed = false;

	size_t i;
	for (i = 0 ; i < clusters.GetCount() ; i++)
	{
		wxString nsp = qtIdent(wxT("_") + clusters.Item(i));

		pgSetIterator sets(GetConnection(),
		                   wxT("SELECT tab_set, sub_provider, ") + nsp + wxT(".getlocalnodeid(") + qtDbString(wxT("_") + clusters.Item(i)) + wxT(") AS localnode\n")
		                   wxT("  FROM ") + nsp + wxT(".sl_table\n")
		                   wxT("  LEFT JOIN ") + nsp + wxT(".sl_subscribe ON sub_set=tab_set\n")
		                   wxT(" WHERE tab_reloid = ") + GetOidStr());

		if (sets.RowsLeft())
		{
			if (clusterName)
				*clusterName = clusters.Item(i);
			if (setId)
				*setId = sets.GetLong(wxT("tab_set"));
			if (isSubscribed)
				return REPLICATIONSTATUS_MULTIPLY_PUBLISHED;

			long provider = sets.GetLong(wxT("sub_provider"));
			if (provider)
			{
				if (provider != sets.GetLong(wxT("localnode")))
					return REPLICATIONSTATUS_REPLICATED;

				isSubscribed = true;

			}
		}
	}
	if (isSubscribed)
		return REPLICATIONSTATUS_SUBSCRIBED;

	return REPLICATIONSTATUS_NONE;
}


wxString pgTable::GetHelpPage(bool forCreate) const
{
	if (forCreate)
		return wxT("pg/sql-createtable");
	else
		return wxT("pg/sql-altertable");
}


bool pgTable::DropObject(wxFrame *frame, ctlTree *browser, bool cascaded)
{
	wxString sql = wxT("DROP TABLE ") + this->GetSchema()->GetQuotedIdentifier() + wxT(".") + this->GetQuotedIdentifier();
	if (cascaded)
		sql += wxT(" CASCADE");
	return GetDatabase()->ExecuteVoid(sql);
}


bool pgTable::Truncate(bool cascaded)
{
	wxString sql = wxT("TRUNCATE TABLE ") + this->GetSchema()->GetQuotedIdentifier() + wxT(".") + this->GetQuotedIdentifier();
	if (cascaded)
		sql += wxT(" CASCADE");
	return GetDatabase()->ExecuteVoid(sql);
}


bool pgTable::ResetStats()
{
	wxString sql = wxT("SELECT pg_stat_reset_single_table_counters(")
	               + NumToStr(this->GetOid())
	               + wxT(")");
	return GetDatabase()->ExecuteVoid(sql);
}


void pgTable::AppendStuff(wxString &sql, ctlTree *browser, pgaFactory &factory)
{
	wxString tmp;

	pgCollection *collection = browser->FindCollection(factory, GetId());
	if (collection)
	{
		tmp += wxT("\n");
		collection->ShowTreeDetail(browser);

		treeObjectIterator idxIt(browser, collection);
		pgObject *obj;
		while ((obj = idxIt.GetNextObject()) != 0)
		{
			obj->ShowTreeDetail(browser);

			tmp += obj->GetSql(browser) + wxT("\n");
		}
	}

	if (!tmp.IsEmpty() && tmp != wxT("\n"))
		sql += tmp;
}

void pgTable::AppendStuffNoSql(wxString &sql, ctlTree *browser, pgaFactory &factory)
{
	pgCollection *collection = browser->FindCollection(factory, GetId());
	if (collection)
	{
		collection->ShowTreeDetail(browser);

		treeObjectIterator idxIt(browser, collection);
		pgObject *obj;
		while ((obj = idxIt.GetNextObject()) != 0)
		{
			obj->ShowTreeDetail(browser);
		}
	}
}



wxString pgTable::GetSql(ctlTree *browser)
{
	wxString colDetails, conDetails;
	wxString prevComment;
	wxString cols_sql = wxEmptyString;
	wxString cols_type = wxEmptyString;
	wxString columnPrivileges;
	wxString defpkcluster;
	if (sql.IsNull())
	{
		// make sure all kids are appended
		ShowTreeDetail(browser);
		sql = wxT("-- Table: ") + GetQuotedFullIdentifier() + wxT("\n\n")
		      + wxT("-- DROP TABLE ") + GetQuotedFullIdentifier() + wxT(";")
		      + wxT("\n\nCREATE ");
		if (GetUnlogged())
			sql +=  wxT("UNLOGGED ");
		sql += wxT("TABLE ") + GetQuotedFullIdentifier();

		// of type (9.0 material)
		if (ofTypeOid > 0)
			sql += wxT("\nOF ") + qtIdent(ofType);

		// Get a count of the constraints.
		int consCount = 0;
		pgCollection *constraints = browser->FindCollection(primaryKeyFactory, GetId());
		if (constraints)
			consCount = browser->GetChildrenCount(constraints->GetId());

		// Get the columns
		pgCollection *columns = browser->FindCollection(columnFactory, GetId());
		if (columns)
		{
			columns->ShowTreeDetail(browser);
			treeObjectIterator colIt1(browser, columns);
			treeObjectIterator colIt2(browser, columns);


			int lastRealCol = 0;
			int currentCol = 0;
			pgColumn *column;
			// Iterate the columns to find the last 'real' one
			while ((column = (pgColumn *)colIt1.GetNextObject()) != 0)
			{
				if (currentCol==0) {cols_type+=column->GetDisplayName()+wxT(" ")+column->GetQuotedTypename();
				} else cols_type+=wxT(", ")+column->GetDisplayName()+wxT(" ")+column->GetQuotedTypename();
				currentCol++;
				//AppendIfFilled(cols_type,wxT(","),cols_type);
				if (column->GetInheritedCount() == 0)
					lastRealCol = currentCol;
			}
			cols_type=wxT("-- (")+cols_type+wxT(")");
			// Now build the actual column list
			int colCount = 0;
			while ((column = (pgColumn *)colIt2.GetNextObject()) != 0)
			{
				column->ShowTreeDetail(browser);
				if (column->GetColNumber() > 0)
				{
					if (colCount)
					{
						// Only add a comma if this isn't the last 'real' column, or if there are constraints
						if (colCount != lastRealCol || consCount)
							cols_sql += wxT(",");
						if (!prevComment.IsEmpty())
							cols_sql += wxT(" -- ") + firstLineOnly(prevComment);

						cols_sql += wxT("\n");
					}

					if (column->GetInheritedCount() > 0)
					{
						if (!column->GetIsLocal())
						{
							cols_sql += wxString::Format(wxT("-- %s "), _("Inherited"))
							            + wxT("from table ") +  column->GetInheritedTableName() + wxT(":");
						}
					}

					if (ofTypeOid > 0)
					{
						if (column->GetDefinition().Length() == 0)
						{
							cols_sql += wxString::Format(wxT("-- %s "), _("Inherited"))
							            + wxT("from type ") +  ofType + wxT(": ")
							            + column->GetQuotedIdentifier();
						}
						else
						{
							cols_sql += wxT("  ") + column->GetQuotedIdentifier() + wxT(" WITH OPTIONS ")
							            + column->GetDefinition();
						}
					}
					else
					{
						cols_sql += wxT("  ") + column->GetQuotedIdentifier() + wxT(" ")
						            + column->GetDefinition();
					}

					prevComment = column->GetComment();

					// Whilst we are looping round the columns, grab their comments as well.
					colDetails += column->GetCommentSql();
					if (colDetails.Length() > 0)
						if (colDetails.Last() != '\n')
							colDetails += wxT("\n");
					if (!GetDatabase()->BackendMinimumVersion(16, 0))
						colDetails += column->GetStorageSql();
					if (colDetails.Length() > 0)
						if (colDetails.Last() != '\n')
							colDetails += wxT("\n");
					colDetails += column->GetAttstattargetSql();
					if (colDetails.Length() > 0)
						if (colDetails.Last() != '\n')
							colDetails += wxT("\n");
					colDetails += column->GetVariablesSql();
					if (colDetails.Length() > 0)
						if (colDetails.Last() != '\n')
							colDetails += wxT("\n");

					colCount++;
					columnPrivileges += column->GetPrivileges();
				}
			}
		}

		// Now iterate the constraints
		if (constraints)
		{
			constraints->ShowTreeDetail(browser);
			treeObjectIterator consIt(browser, constraints);

			pgObject *data;

			while ((data = consIt.GetNextObject()) != 0)
			{
				data->ShowTreeDetail(browser);

				cols_sql += wxT(",");

				if (!prevComment.IsEmpty())
					cols_sql += wxT(" -- ") + firstLineOnly(prevComment);

				cols_sql += wxT("\n  CONSTRAINT ") + data->GetQuotedIdentifier()
				            + wxT(" ") + data->GetTypeName().Upper()
				            + wxT(" ") ;

				prevComment = data->GetComment();
				if (!data->GetComment().IsEmpty())
					conDetails += wxT("COMMENT ON CONSTRAINT ") + data->GetQuotedIdentifier() +
					              wxT(" ON ") + GetQuotedFullIdentifier() +
					              wxT(" IS ") + qtDbString(data->GetComment()) + wxT(";\n");

				switch (data->GetMetaType())
				{
					case PGM_PRIMARYKEY:
					case PGM_UNIQUE:
					case PGM_EXCLUDE:
						cols_sql += ((pgIndexConstraint *)data)->GetDefinition();
						if (data->GetMetaType()== PGM_PRIMARYKEY) defpkcluster= ((pgIndexConstraint*)data)->GetDefinitionCluster();
						break;
					case PGM_FOREIGNKEY:
						cols_sql += ((pgForeignKey *)data)->GetDefinition(PGM_TABLE);
						break;
					case PGM_CHECK:
						cols_sql += wxT("(") + ((pgCheck *)data)->GetDefinition() + wxT(")");
						if (GetDatabase()->BackendMinimumVersion(9, 2) && ((pgCheck *)data)->GetNoInherit())
							cols_sql += wxT(" NO INHERIT");
						if (GetDatabase()->BackendMinimumVersion(9, 2) && !((pgCheck *)data)->GetValid())
							cols_sql += wxT(" NOT VALID");
						break;
				}
			}
		}
		if (!prevComment.IsEmpty())
			cols_sql += wxT(" -- ") + firstLineOnly(prevComment);
		if (GetConnection()->BackendMinimumVersion(10, 0)&&!GetPartExp().IsEmpty())
		{
			sql += wxT("\n  PARTITION OF ") + GetQuotedInheritedTables() + wxT("(\n") + cols_sql + wxT("\n)");
			sql += wxT(" ") + GetPartExp() + wxT(" ");
		} else
		{
			sql += wxT("\n(\n") + cols_sql + wxT("\n)");
		}
		if (GetInheritedTableCount())
		{
			if (GetConnection()->BackendMinimumVersion(10, 0)&&!GetPartExp().IsEmpty()) {
				
			} else
			{
			sql += wxT("\nINHERITS (") + GetQuotedInheritedTables() + wxT(")");
			}
		}
		if (GetConnection()->BackendMinimumVersion(10, 0)&&!GetPartKeyDef().IsEmpty())
		{
			sql += wxT("\nPARTITION BY ") + GetPartKeyDef() + wxT(" ");
		}
		if (GetConnection()->BackendMinimumVersion(8, 2))
		{
			wxString sqlopt=wxEmptyString;
			if (GetFillFactor().Length() > 0)
				sqlopt += wxT("\n  FILLFACTOR=") + GetFillFactor() + wxT(", ");
			if (GetAppendOnly().Length() > 0)
				sqlopt += wxT("APPENDONLY=") + GetAppendOnly() + wxT(", ");
			if (GetCompressLevel().Length() > 0)
				sqlopt += wxT("COMPRESSLEVEL=") + GetCompressLevel() + wxT(", ");
			if (GetOrientation().Length() > 0)
				sqlopt += wxT("ORIENTATION=") + GetOrientation() + wxT(", ");
			if (GetCompressType().Length() > 0)
				sqlopt += wxT("COMPRESSTYPE=") + GetCompressType() + wxT(", ");
			if (GetBlocksize().Length() > 0)
				sqlopt += wxT("BLOCKSIZE=") + GetBlocksize() + wxT(", ");
			if (GetChecksum().Length() > 0)
				sqlopt += wxT("CHECKSUM=") + GetChecksum() + wxT(", ");
			if (GetConnection()->BackendMinimumVersion(12, 0)) {
				if (!sqlopt.IsEmpty()) sqlopt=sqlopt.BeforeLast(',');
			} else {
					if (GetHasOids())
						sqlopt +=  wxT("\n  OIDS=TRUE");
					else
						sqlopt +=  wxT("\n  OIDS=FALSE");
				    }

			if(GetConnection()->BackendMinimumVersion(8, 4))
			{
				if (GetCustomAutoVacuumEnabled())
				{
					if (GetAutoVacuumEnabled() == 1)
						sqlopt += wxT(",\n  autovacuum_enabled=true");
					else if (GetCustomAutoVacuumEnabled() == 0)
						sqlopt += wxT(",\n  autovacuum_enabled=false");
					if (!GetAutoVacuumVacuumThreshold().IsEmpty())
					{
						sqlopt += wxT(",\n  autovacuum_vacuum_threshold=") + GetAutoVacuumVacuumThreshold();
					}
					if (!GetAutoVacuumVacuumScaleFactor().IsEmpty())
					{
						sqlopt += wxT(",\n  autovacuum_vacuum_scale_factor=") + GetAutoVacuumVacuumScaleFactor();
					}
					if (!GetAutoVacuumAnalyzeThreshold().IsEmpty())
					{
						sqlopt += wxT(",\n  autovacuum_analyze_threshold=") + GetAutoVacuumAnalyzeThreshold();
					}
					if (!GetAutoVacuumAnalyzeScaleFactor().IsEmpty())
					{
						sqlopt += wxT(",\n  autovacuum_analyze_scale_factor=") + GetAutoVacuumAnalyzeScaleFactor();
					}
					if (!GetAutoVacuumVacuumCostDelay().IsEmpty())
					{
						sqlopt += wxT(",\n  autovacuum_vacuum_cost_delay=") + GetAutoVacuumVacuumCostDelay();
					}
					if (!GetAutoVacuumVacuumCostLimit().IsEmpty())
					{
						sqlopt += wxT(",\n  autovacuum_vacuum_cost_limit=") + GetAutoVacuumVacuumCostLimit();
					}
					if (!GetAutoVacuumFreezeMinAge().IsEmpty())
					{
						sqlopt += wxT(",\n  autovacuum_freeze_min_age=") + GetAutoVacuumFreezeMinAge();
					}
					if (!GetAutoVacuumFreezeMaxAge().IsEmpty())
					{
						sqlopt += wxT(",\n  autovacuum_freeze_max_age=") + GetAutoVacuumFreezeMaxAge();
					}
					if (!GetAutoVacuumFreezeTableAge().IsEmpty())
					{
						sqlopt += wxT(",\n  autovacuum_freeze_table_age=") + GetAutoVacuumFreezeTableAge();
					}
				}
				if (GetHasToastTable() && GetToastCustomAutoVacuumEnabled())
				{
					if (GetToastAutoVacuumEnabled() == 1)
						sqlopt += wxT(",\n  toast.autovacuum_enabled=true");
					else if (GetToastAutoVacuumEnabled() == 0)
						sqlopt += wxT(",\n  toast.autovacuum_enabled=false");
					if (!GetToastAutoVacuumVacuumThreshold().IsEmpty())
					{
						sqlopt += wxT(",\n  toast.autovacuum_vacuum_threshold=") + GetToastAutoVacuumVacuumThreshold();
					}
					if (!GetToastAutoVacuumVacuumScaleFactor().IsEmpty())
					{
						sqlopt += wxT(",\n  toast.autovacuum_vacuum_scale_factor=") + GetToastAutoVacuumVacuumScaleFactor();
					}
					if (!GetToastAutoVacuumVacuumCostDelay().IsEmpty())
					{
						sqlopt += wxT(",\n  toast.autovacuum_vacuum_cost_delay=") + GetToastAutoVacuumVacuumCostDelay();
					}
					if (!GetToastAutoVacuumVacuumCostLimit().IsEmpty())
					{
						sqlopt += wxT(",\n  toast.autovacuum_vacuum_cost_limit=") + GetToastAutoVacuumVacuumCostLimit();
					}
					if (!GetToastAutoVacuumFreezeMinAge().IsEmpty())
					{
						sqlopt += wxT(",\n  toast.autovacuum_freeze_min_age=") + GetToastAutoVacuumFreezeMinAge();
					}
					if (!GetToastAutoVacuumFreezeMaxAge().IsEmpty())
					{
						sqlopt += wxT(",\n  toast.autovacuum_freeze_max_age=") + GetToastAutoVacuumFreezeMaxAge();
					}
					if (!GetToastAutoVacuumFreezeTableAge().IsEmpty())
					{
						sqlopt += wxT(",\n  toast.autovacuum_freeze_table_age=") + GetToastAutoVacuumFreezeTableAge();
					}
				}
			}
			wxRegEx reg("autovacuum_vacuum_insert_scale_factor=[0-9.]+|autovacuum_vacuum_insert_threshold=[0-9]+|vacuum_index_cleanup=[a-z]+|vacuum_truncate=[a-z]+|parallel_workers=[0-9]+|toast_tuple_target=[0-9]+|log_autovacuum_min_duration=[0-9]+|user_catalog_table=[a-z]+");
			wxString relopt=GetRelOptions();
			wxString o;
			size_t start, len;
			while ( reg.Matches(relopt) )
			{
				reg.GetMatch(&start, &len, 0);
				o=reg.GetMatch(relopt, 0);
				AppendIfFilled(sqlopt, ",\n  ", o);
				relopt = relopt.Mid (start + len);
			}
			relopt=GetToastRelOptions();
			while ( reg.Matches(relopt) )
			{
				reg.GetMatch(&start, &len, 0);
				o="toast." + reg.GetMatch(relopt, 0);
				AppendIfFilled(sqlopt, ",\n  ", o);
				relopt = relopt.Mid (start + len);
			}
			if (!sqlopt.IsEmpty()) {
				sql += wxT("\nWITH (");
				if (sqlopt.Index(',')==0) sqlopt=sqlopt.Mid(1);
				sql += sqlopt;
				sql += wxT("\n)");
			}
		}
		else
		{
			if (GetHasOids())
				sql +=  wxT("\nWITH OIDS");
			else
				sql +=  wxT("\nWITHOUT OIDS");
		}

		if (GetConnection()->BackendMinimumVersion(8, 0) && tablespace != GetDatabase()->GetDefaultTablespace())
			sql += wxT("\nTABLESPACE ") + qtIdent(tablespace);

		if (GetConnection()->GetIsGreenplum())
		{
			// Add Greenplum DISTRIBUTED BY
			if (distributionIsRandom)
			{
				sql += wxT("\nDISTRIBUTED RANDOMLY");
			}
			else if (GetDistributionColNumbers().Length() == 0)
			{
				// catalog table or other non-distributed table
			}
			else
			{
				// convert list of columns numbers to column names
				wxStringTokenizer collist(GetDistributionColNumbers(), wxT(","));
				wxString cn;
				wxString distributionColumns;
				while (collist.HasMoreTokens())
				{
					cn = collist.GetNextToken();
					pgSet *set = ExecuteSet(
					                 wxT("SELECT attname\n")
					                 wxT("  FROM pg_attribute\n")
					                 wxT(" WHERE attrelid=") + GetOidStr() + wxT(" AND attnum IN (") + cn + wxT(")"));
					if (set)
					{
						if (!distributionColumns.IsNull())
						{
							distributionColumns += wxT(", ");
						}
						distributionColumns += qtIdent(set->GetVal(0));
						delete set;
					}
				}

				sql += wxT("\nDISTRIBUTED BY (");
				sql += distributionColumns;

				sql += wxT(")");
			}

			if (GetIsPartitioned())
				if (GetConnection()->BackendMinimumVersion(8, 2, 9) && GetConnection()->GetIsGreenplum())
					if (GetPartitionDef().Length() == 0)
					{
						wxString query = wxT("SELECT pg_get_partition_def(");
						query += GetOidStr();
						query += wxT(", true) ");
						wxString partition_def = GetDatabase()->ExecuteScalar(query);
						iSetPartitionDef(partition_def);
						// pg_get_partition_def() doesn't work on partitions
						if (GetPartitionDef().Length() == 0)
							iSetPartitionDef(wxT("-- This partition has subpartitions"));
					}
			if (partitionDef.Length() > 0)
				sql += wxT("\n") + partitionDef + wxT("\n");


		}


		sql += wxT(";\n")
		       + GetOwnerSql(7, 3);

		if (GetConnection()->BackendMinimumVersion(17, 0))
			sql += GetGrant(wxT("arwdDxtm"));
		else if (GetConnection()->BackendMinimumVersion(8, 4))
			sql += GetGrant(wxT("arwdDxt"));
		else if (GetConnection()->BackendMinimumVersion(8, 2))
			sql += GetGrant(wxT("arwdxt"));
		else
			sql += GetGrant(wxT("arwdRxt"));
		wxString st=GetStatExt();
		if (!st.IsEmpty()) sql += st + wxT("\n");
		sql += GetCommentSql();

		if (GetConnection()->BackendMinimumVersion(9, 1))
			sql += GetSeqLabelsSql();

		// Column/constraint comments
		if (!colDetails.IsEmpty())
			sql += colDetails + wxT("\n");

		if (!conDetails.IsEmpty())
			sql += conDetails + wxT("\n");

		if (!columnPrivileges.IsEmpty())
		{
			sql += columnPrivileges + wxT("\n");
		}

		AppendStuff(sql, browser, indexFactory);
		if (!defpkcluster.IsEmpty()) sql += defpkcluster;
		AppendStuff(sql, browser, ruleFactory);
		AppendStuff(sql, browser, triggerFactory);

		/*
		 * Disable adding partitions until that code works.
		 *
		if (partitionDef.Length() > 0)
		{
		    AppendStuffNoSql(sql, browser, partitionFactory);
		}
		 */
		sql+=cols_type;
	}
	
	return sql;
}

wxString pgTable::GetCoveringIndex(ctlTree *browser, const wxString &collist)
{
	// delivers the name of the index which covers the named columns
	wxCookieType cookie;

	wxTreeItemId collItem = browser->GetFirstChild(GetId(), cookie);
	while (collItem)
	{
		pgObject *data = browser->GetObject(collItem);
		if (data && data->IsCollection() && (data->GetMetaType() == PGM_CONSTRAINT || data->GetMetaType() == PGM_INDEX))
		{
			wxCookieType cookie2;
			wxTreeItemId item = browser->GetFirstChild(collItem, cookie2);
			while (item)
			{
				pgIndex *index = (pgIndex *)browser->GetObject(item);
				if (index && (index->GetMetaType() == PGM_INDEX || index->GetMetaType() == PGM_PRIMARYKEY
				              || index->GetMetaType() == PGM_UNIQUE || index->GetMetaType() == PGM_EXCLUDE))
				{
					index->ShowTreeDetail(browser);
					if (collist == index->GetColumns() ||
					        collist + wxT(",") == index->GetColumns().Left(collist.Length() + 1))
						return index->GetName();
				}
				item = browser->GetNextChild(collItem, cookie2);
			}
		}
		collItem = browser->GetNextChild(GetId(), cookie);
	}

	return wxEmptyString;
}


wxString pgTable::GetCols(ctlTree *browser, size_t indent, wxString &QMs, bool withQM)
{
	wxString sql;
	wxString line;

	int colcount = 0;
	pgCollection *columns = browser->FindCollection(columnFactory, GetId());
	if (columns)
	{
		columns->ShowTreeDetail(browser);
		treeObjectIterator colIt(browser, columns);

		pgColumn *column;
		while ((column = (pgColumn *)colIt.GetNextObject()) != 0)
		{
			column->ShowTreeDetail(browser);
			if (column->GetColNumber() > 0)
			{
				if (colcount++)
				{
					line += wxT(", ");
					QMs += wxT(", ");
				}
				if (line.Length() > 60)
				{
					if (!sql.IsEmpty())
					{
						sql += wxT("\n") + wxString(' ', indent);
					}
					sql += line;
					line = wxEmptyString;
					QMs += wxT("\n") + wxString(' ', indent);
				}

				line += column->GetQuotedIdentifier();
				if (withQM)
					line += wxT("=?");
				QMs += wxT("?");
			}
		}
	}

	if (!line.IsEmpty())
	{
		if (!sql.IsEmpty())
			sql += wxT("\n") + wxString(' ', indent);
		sql += line;
	}
	return sql;
}

pgCollection *pgTable::GetColumnCollection(ctlTree *browser)
{
	pgCollection *columns = browser->FindCollection(columnFactory, GetId());
	return columns;
}

pgCollection *pgTable::GetConstraintCollection(ctlTree *browser)
{
	pgCollection *constraints = browser->FindCollection(constraintFactory, GetId());
	return constraints;
}

wxString pgTable::GetSelectSql(ctlTree *browser)
{
	wxString qms;
	wxString sql =
	    wxT("SELECT ") + GetCols(browser, 7, qms, false) + wxT("\n")
	    wxT("  FROM ") + GetQuotedFullIdentifier() + wxT(";\n");
	return sql;
}


wxString pgTable::GetInsertSql(ctlTree *browser)
{
	wxString qms;
	wxString sql =
	    wxT("INSERT INTO ") + GetQuotedFullIdentifier() + wxT("(\n")
	    wxT("            ") + GetCols(browser, 12, qms, false) + wxT(")\n")
	    wxT("    VALUES (") + qms + wxT(");\n");
	return sql;
}


wxString pgTable::GetUpdateSql(ctlTree *browser)
{
	wxString qms;
	wxString sql =
	    wxT("UPDATE ") + GetQuotedFullIdentifier() + wxT("\n")
	    wxT("   SET ") + GetCols(browser, 7, qms, true) + wxT("\n")
	    wxT(" WHERE <condition>;\n");
	return sql;
}

wxString pgTable::GetDeleteSql(ctlTree *browser)
{
	wxString sql =
	    wxT("DELETE FROM ") + GetQuotedFullIdentifier() + wxT("\n")
	    wxT(" WHERE <condition>;\n");
	return sql;
}

bool pgTable::EnableTriggers(const bool b)
{
	wxString sql = wxT("ALTER TABLE ") + GetQuotedFullIdentifier() + wxT(" ");

	if (!b)
		sql += wxT("DISABLE");
	else
		sql += wxT("ENABLE");

	sql += wxT(" TRIGGER ALL");

	return GetDatabase()->ExecuteVoid(sql);
}

void pgTable::UpdateRows()
{
	pgSet *props = ExecuteSet(wxT("SELECT count(*) AS rows FROM ONLY ") + GetQuotedFullIdentifier());
	if (props)
	{
		rows = props->GetLongLong(0);
		delete props;
		rowsCounted = true;
	}
}


void pgTable::UpdateInheritance()
{
	// not checked so far
	pgSet *props = ExecuteSet(
	                   wxT("SELECT c.oid, c.relname , nspname\n")
	                   wxT("  FROM pg_inherits i\n")
	                   wxT("  JOIN pg_class c ON c.oid = i.inhparent\n")
	                   wxT("  JOIN pg_namespace n ON n.oid=c.relnamespace\n")
	                   wxT(" WHERE i.inhrelid = ") + GetOidStr() + wxT("\n")
	                   wxT(" ORDER BY inhseqno"));
	if (props)
	{
		inheritedTableCount = 0;
		inheritedTables = wxT("");
		while (!props->Eof())
		{
			if (inheritedTableCount)
			{
				inheritedTables += wxT(", ");
				quotedInheritedTables += wxT(", ");
			}
			inheritedTables += props->GetVal(wxT("relname"));
			quotedInheritedTables += GetQuotedSchemaPrefix(props->GetVal(wxT("nspname")))
			                         + qtIdent(props->GetVal(wxT("relname")));
			quotedInheritedTablesList.Add(GetQuotedSchemaPrefix(props->GetVal(wxT("nspname")))
			                              + qtIdent(props->GetVal(wxT("relname"))));
			inheritedTablesOidList.Add(props->GetVal(wxT("oid")));
			props->MoveNext();
			inheritedTableCount++;
		}
		delete props;
	}
}





void pgTable::ShowTreeDetail(ctlTree *browser, frmMain *form, ctlListView *properties, ctlSQLBox *sqlPane)
{
	if (!expandedKids)
	{
		expandedKids = true;

		browser->RemoveDummyChild(this);

		// Log
		wxLogInfo(wxT("Adding child object to table %s"), GetIdentifier().c_str());

		browser->AppendCollection(this, columnFactory);
		browser->AppendCollection(this, constraintFactory);
		browser->AppendCollection(this, indexFactory);
		browser->AppendCollection(this, ruleFactory);
		browser->AppendCollection(this, triggerFactory);

		if (GetConnection() != 0 && GetConnection()->GetIsGreenplum() && GetIsPartitioned())
			browser->AppendCollection(this, partitionFactory);

		if (GetConnection() != 0 && (!GetPartKeyDef().IsEmpty()||GetIsPartitioned()))
			browser->AppendCollection(this, pg_partitionFactory);

		
		// convert list of columns numbers to column names
		wxStringTokenizer collist(GetPrimaryKeyColNumbers(), wxT(","));
		wxString cn;

		while (collist.HasMoreTokens())
		{
			cn = collist.GetNextToken();
			pgSet *set = ExecuteSet(
			                 wxT("SELECT attname\n")
			                 wxT("  FROM pg_attribute\n")
			                 wxT(" WHERE attrelid=") + GetOidStr() + wxT(" AND attnum IN (") + cn + wxT(")"));
			if (set)
			{
				if (!primaryKey.IsNull())
				{
					quotedPrimaryKey += wxT(", ");
					primaryKey += wxT(", ");
				}
				primaryKey += set->GetVal(0);
				quotedPrimaryKey += qtIdent(set->GetVal(0));
				delete set;
			}
		}

		if (settings->GetAutoRowCountThreshold() >= GetEstimatedRows())
			UpdateRows();

		UpdateInheritance();
	}

	if (properties)
	{
		CreateListColumns(properties);

		properties->AppendItem(_("Name"), GetName());
		if (GetConnection() != 0 && GetConnection()->GetIsGreenplum())
		{
			gpPartition *p = dynamic_cast<gpPartition *>(this);
			if (p != 0)
				properties->AppendItem(_("Partition Name"), p->GetPartitionName());
		}
		properties->AppendItem(_("OID"), GetOid());
		properties->AppendItem(_("Owner"), GetOwner());
		if (GetConnection()->BackendMinimumVersion(8, 0))
			properties->AppendItem(_("Tablespace"), tablespace);
		properties->AppendItem(_("ACL"), GetAcl());
		if (GetConnection()->BackendMinimumVersion(9, 0))
			properties->AppendItem(_("Of type"), ofType);
		if (GetPrimaryKey().IsNull())
			properties->AppendItem(_("Primary key"), _("<no primary key>"));
		else
			properties->AppendItem(_("Primary key"), GetPrimaryKey());

		properties->AppendItem(_("Rows (estimated)"), GetEstimatedRows());

		if (GetConnection()->BackendMinimumVersion(8, 2))
			properties->AppendItem(_("Fill factor"), GetFillFactor());

		if (rowsCounted)
			properties->AppendItem(_("Rows (counted)"), rows);
		else
			properties->AppendItem(_("Rows (counted)"), _("not counted"));

		long setId;
		wxString clusterName;
		long repStat = GetReplicationStatus(browser, &clusterName, &setId);

		wxString clusterInfo;
		clusterInfo.Printf(_("Cluster \"%s\", set %ld"), clusterName.c_str(), setId);

		wxString repString;
		switch (repStat)
		{
			case REPLICATIONSTATUS_SUBSCRIBED:
				repString = _("Published");
				break;
			case REPLICATIONSTATUS_REPLICATED:
				repString = _("Replicated");
				break;
			case REPLICATIONSTATUS_MULTIPLY_PUBLISHED:
				repString = _("Replicated");
				clusterName = _("Multiple clusters");
				break;
			default:
				break;
		}
		if (!repString.IsEmpty())
			properties->AppendItem(repString, clusterInfo);

		properties->AppendYesNoItem(_("Inherits tables"), GetHasSubclass());
		properties->AppendItem(_("Inherited tables count"), GetInheritedTableCount());
		if (GetInheritedTableCount())
			properties->AppendItem(_("Inherited tables"), GetInheritedTables());
		if (GetConnection()->BackendMinimumVersion(9, 1))
			properties->AppendYesNoItem(_("Unlogged?"), GetUnlogged());
		if (!GetConnection()->BackendMinimumVersion(12, 0))
			properties->AppendYesNoItem(_("Has OIDs?"), GetHasOids());
		if (!GetConnection()->BackendMinimumVersion(12, 0))
			properties->AppendYesNoItem(_("System table?"), GetSystemObject());
		if (GetConnection()->GetIsPgProEnt() && GetRatio().Len()>0)
			properties->AppendItem(_("CFS fragmentation"), GetRatio());
		if (GetConnection()->HasFeature(FEATURE_TRACK_COMMIT_TS) && GetCreateTableTS().Len()>0)
			properties->AppendItem(_("Create table timestamp"), GetCreateTableTS());

		/* Custom AutoVacuum Settings */
		if (GetConnection()->BackendMinimumVersion(8, 4) && GetCustomAutoVacuumEnabled())
		{
			if (GetAutoVacuumEnabled() != 2)
			{
				properties->AppendItem(_("Table auto-vacuum enabled?"), GetAutoVacuumEnabled() == 1 ? _("Yes") : _("No"));
			}
			if (!GetAutoVacuumVacuumThreshold().IsEmpty())
				properties->AppendItem(_("Table auto-vacuum VACUUM base threshold"), GetAutoVacuumVacuumThreshold());
			if (!GetAutoVacuumVacuumScaleFactor().IsEmpty())
				properties->AppendItem(_("Table auto-vacuum VACUUM scale factor"), GetAutoVacuumVacuumScaleFactor());
			if (!GetAutoVacuumAnalyzeThreshold().IsEmpty())
				properties->AppendItem(_("Table auto-vacuum ANALYZE base threshold"), GetAutoVacuumAnalyzeThreshold());
			if (!GetAutoVacuumAnalyzeScaleFactor().IsEmpty())
				properties->AppendItem(_("Table auto-vacuum ANALYZE scale factor"), GetAutoVacuumAnalyzeScaleFactor());
			if (!GetAutoVacuumVacuumCostDelay().IsEmpty())
				properties->AppendItem(_("Table auto-vacuum VACUUM cost delay"), GetAutoVacuumVacuumCostDelay());
			if (!GetAutoVacuumVacuumCostLimit().IsEmpty())
				properties->AppendItem(_("Table auto-vacuum VACUUM cost limit"), GetAutoVacuumVacuumCostLimit());
			if (!GetAutoVacuumFreezeMinAge().IsEmpty())
				properties->AppendItem(_("Table auto-vacuum FREEZE minimum age"), GetAutoVacuumFreezeMinAge());
			if (!GetAutoVacuumFreezeMaxAge().IsEmpty())
				properties->AppendItem(_("Table auto-vacuum FREEZE maximum age"), GetAutoVacuumFreezeMaxAge());
			if (!GetAutoVacuumFreezeTableAge().IsEmpty())
				properties->AppendItem(_("Table auto-vacuum FREEZE table age"), GetAutoVacuumFreezeTableAge());
		}

		/* Custom TOAST-TABLE AutoVacuum Settings */
		if (GetConnection()->BackendMinimumVersion(8, 4) &&
		        GetHasToastTable() &&
		        GetToastCustomAutoVacuumEnabled())
		{
			if (GetToastAutoVacuumEnabled() != 2)
			{
				properties->AppendItem(_("Toast auto-vacuum enabled?"), GetToastAutoVacuumEnabled() == 1 ? _("Yes") : _("No"));
			}
			if (!GetToastAutoVacuumVacuumThreshold().IsEmpty())
				properties->AppendItem(_("Toast auto-vacuum VACUUM base threshold"), GetToastAutoVacuumVacuumThreshold());
			if (!GetToastAutoVacuumVacuumScaleFactor().IsEmpty())
				properties->AppendItem(_("Toast auto-vacuum VACUUM scale factor"), GetToastAutoVacuumVacuumScaleFactor());
			if (!GetToastAutoVacuumVacuumCostDelay().IsEmpty())
				properties->AppendItem(_("Toast auto-vacuum VACUUM cost delay"), GetToastAutoVacuumVacuumCostDelay());
			if (!GetToastAutoVacuumVacuumCostLimit().IsEmpty())
				properties->AppendItem(_("Toast auto-vacuum VACUUM cost limit"), GetToastAutoVacuumVacuumCostLimit());
			if (!GetToastAutoVacuumFreezeMinAge().IsEmpty())
				properties->AppendItem(_("Toast auto-vacuum FREEZE minimum age"), GetToastAutoVacuumFreezeMinAge());
			if (!GetToastAutoVacuumFreezeMaxAge().IsEmpty())
				properties->AppendItem(_("Toast auto-vacuum FREEZE maximum age"), GetToastAutoVacuumFreezeMaxAge());
			if (!GetToastAutoVacuumFreezeTableAge().IsEmpty())
				properties->AppendItem(_("Toast auto-vacuum FREEZE table age"), GetToastAutoVacuumFreezeTableAge());
		}
		properties->AppendItem(_("Comment"), firstLineOnly(GetComment()));

		if (!GetLabels().IsEmpty())
		{
			wxArrayString seclabels = GetProviderLabelArray();
			if (seclabels.GetCount() > 0)
			{
				for (unsigned int index = 0 ; index < seclabels.GetCount() - 1 ; index += 2)
				{
					properties->AppendItem(seclabels.Item(index), seclabels.Item(index + 1));
				}
			}
		}
	}
	if (form && GetVacuumHint() && !hintShown)
	{
		ShowHint(form, false);
	}
}


bool pgTable::GetCanHint()
{
	return (GetVacuumHint() || primaryKey.IsEmpty());
}


bool pgTable::GetVacuumHint()
{
	bool canHint = false;

	if (rowsCounted)
	{
		if (!estimatedRows || (estimatedRows == 1000 && rows.GetValue() != 1000))
			canHint = (rows >= 20);
		else
		{
			double rowsDbl = (wxLongLong_t)rows.GetValue();
			double quot = rowsDbl * 10. / estimatedRows;
			canHint = ((quot > 12 || quot < 8) && (rowsDbl < estimatedRows - 20. || rowsDbl > estimatedRows + 20.));
		}
	}
	else if (estimatedRows == 1000)
	{
		canHint = true;
	}
	return canHint;
}


void pgTable::ShowHint(frmMain *form, bool force)
{
	hintShown = true;
	int rc;

	if (force)
	{
		wxArrayString hints;
		if (GetVacuumHint())
			hints.Add(HINT_VACUUM);
		if (primaryKey.IsEmpty())
			hints.Add(HINT_PRIMARYKEY);

		rc = frmHint::ShowHint(form, hints, GetFullIdentifier(), force);
	}
	else
		rc = frmHint::ShowHint(form, HINT_VACUUM, GetFullIdentifier(), force);

	if (rc == HINT_RC_FIX)
	{
		frmMaintenance *frm = new frmMaintenance(form, this);
		frm->Go();
	}
}




pgObject *pgTable::Refresh(ctlTree *browser, const wxTreeItemId item)
{
	pgTable *table = 0;
	pgCollection *coll = browser->GetParentCollection(item);
	if (coll)
		table = (pgTable *)tableFactory.CreateObjects(coll, 0, wxT("\n   AND rel.oid=") + GetOidStr());

	return table;
}


bool pgTable::HasPgstattuple()
{
	return GetConnection()->HasFeature(FEATURE_PGSTATTUPLE);
}

void pgTable::iSetTriggersEnabled(ctlTree *browser, bool enable)
{
	pgCollection *triggers = browser->FindCollection(triggerFactory, GetId());
	if (triggers)
	{
		triggers->ShowTreeDetail(browser);
		treeObjectIterator trgIt(browser, triggers);

		pgTrigger *trigger;
		while ((trigger = (pgTrigger *)trgIt.GetNextObject()) != 0)
		{
			trigger->SetEnabled(browser, enable);
		}
	}
}

///////////////////////////////////////////////////////////


pgTableCollection::pgTableCollection(pgaFactory *factory, pgSchema *sch)
	: pgSchemaObjCollection(factory, sch)
{
}


wxString pgTableCollection::GetTranslatedMessage(int kindOfMessage) const
{
	wxString message = wxEmptyString;

	switch (kindOfMessage)
	{
		case RETRIEVINGDETAILS:
			message = _("Retrieving details on tables");
			break;
		case REFRESHINGDETAILS:
			message = _("Refreshing tables");
			break;
		case GRANTWIZARDTITLE:
			message = _("Privileges for tables");
			break;
		case STATISTICSREPORT:
			message = _("Tables statistics report");
			break;
		case OBJSTATISTICS:
			message = _("Tables statistics");
			break;
		case OBJECTSLISTREPORT:
			message = _("Tables list report");
			break;
	}

	return message;
}


void pgTableCollection::ShowStatistics(frmMain *form, ctlListView *statistics)
{
	ShowStatisticsTables(form, statistics, this);
	return;
	wxLogInfo(wxT("Displaying statistics for tables on %s"), GetSchema()->GetIdentifier().c_str());

	bool hasSize = GetConnection()->HasFeature(FEATURE_SIZE);

	// Add the statistics view columns
	statistics->ClearAll();
	statistics->AddColumn(_("Table Name"));
	if (hasSize)
		statistics->AddColumn(_("Size"), 50);
	if (GetConnection()->GetIsPgProEnt()) statistics->AddColumn(_("CFS %"));
	statistics->AddColumn(_("Live tuples"));


	statistics->AddColumn(_("Tuples inserted"));
	statistics->AddColumn(_("Tuples updated"));
	statistics->AddColumn(_("Tuples deleted"));
	if (GetConnection()->BackendMinimumVersion(8, 3))
	{
		statistics->AddColumn(_("Tuples HOT updated"));
		statistics->AddColumn(_("Dead tuples"));
	}
	if (GetConnection()->BackendMinimumVersion(8, 2))
	{
		statistics->AddColumn(_("Last vacuum"));
		statistics->AddColumn(_("Last autovacuum"));
		statistics->AddColumn(_("Last analyze"));
		statistics->AddColumn(_("Last autoanalyze"));
	}
	if (GetConnection()->BackendMinimumVersion(9, 1))
	{
		statistics->AddColumn(_("Vacuum counter"));
		statistics->AddColumn(_("Autovacuum counter"));
		statistics->AddColumn(_("Analyze counter"));
		statistics->AddColumn(_("Autoanalyze counter"));
	}

	wxString sql = wxT("SELECT st.relname, n_tup_ins, n_tup_upd, n_tup_del");
	if (GetConnection()->BackendMinimumVersion(8, 3))
		sql += wxT(", n_tup_hot_upd, n_live_tup, n_dead_tup");
	if (GetConnection()->BackendMinimumVersion(8, 2))
		sql += wxT(", last_vacuum, last_autovacuum, last_analyze, last_autoanalyze");
	if (GetConnection()->BackendMinimumVersion(9, 1))
		sql += wxT(", vacuum_count, autovacuum_count, analyze_count, autoanalyze_count");
	if (hasSize)
		sql += wxT(", pg_size_pretty(pg_relation_size(st.relid)")
		       wxT(" + CASE WHEN cl.reltoastrelid = 0 THEN 0 ELSE pg_relation_size(cl.reltoastrelid) + COALESCE((SELECT SUM(pg_relation_size(indexrelid)) FROM pg_index WHERE indrelid=cl.reltoastrelid)::int8, 0) END")
		       wxT(" + COALESCE((SELECT SUM(pg_relation_size(indexrelid)) FROM pg_index WHERE indrelid=st.relid)::int8, 0)) AS size");
	if (GetConnection()->GetIsPgProEnt()) sql += wxT(",left((cfs_fragmentation(cl.oid)*100)::text,5)::text AS cfs_ratio");

	sql += wxT("\n  FROM pg_stat_all_tables st")
	       wxT("  JOIN pg_class cl on cl.oid=st.relid\n")
	       wxT(" WHERE schemaname = ") + qtDbString(GetSchema()->GetName())
	       +  wxT("\n ORDER BY relname");

	pgSet *stats = GetDatabase()->ExecuteSet(sql);

	if (stats)
	{
		long pos = 0;
		int i;
		while (!stats->Eof())
		{
			i = 1;
			statistics->InsertItem(pos, stats->GetVal(wxT("relname")), PGICON_STATISTICS);
			if (hasSize)
				statistics->SetItem(pos, i++, stats->GetVal(wxT("size")));
			if (GetConnection()->GetIsPgProEnt()) statistics->SetItem(pos, i++, stats->GetVal(wxT("cfs_ratio")));
			statistics->SetItem(pos, i++, stats->GetVal(wxT("n_tup_ins")));
			statistics->SetItem(pos, i++, stats->GetVal(wxT("n_tup_upd")));
			statistics->SetItem(pos, i++, stats->GetVal(wxT("n_tup_del")));
			if (GetConnection()->BackendMinimumVersion(8, 3))
			{
				statistics->SetItem(pos, i++, stats->GetVal(wxT("n_tup_hot_upd")));
				statistics->SetItem(pos, i++, stats->GetVal(wxT("n_live_tup")));
				statistics->SetItem(pos, i++, stats->GetVal(wxT("n_dead_tup")));
			}
			if (GetConnection()->BackendMinimumVersion(8, 2))
			{
				statistics->SetItem(pos, i++, stats->GetVal(wxT("last_vacuum")));
				statistics->SetItem(pos, i++, stats->GetVal(wxT("last_autovacuum")));
				statistics->SetItem(pos, i++, stats->GetVal(wxT("last_analyze")));
				statistics->SetItem(pos, i++, stats->GetVal(wxT("last_autoanalyze")));
				if (stats->GetVal(wxT("last_analyze")).IsEmpty()&& stats->GetVal(wxT("last_autoanalyze")).IsEmpty())
					statistics->SetItemBackgroundColour(pos, wxColour(wxT("#FF8028")));
			}
			if (GetConnection()->BackendMinimumVersion(9, 1))
			{
				statistics->SetItem(pos, i++, stats->GetVal(wxT("vacuum_count")));
				statistics->SetItem(pos, i++, stats->GetVal(wxT("autovacuum_count")));
				statistics->SetItem(pos, i++, stats->GetVal(wxT("analyze_count")));
				statistics->SetItem(pos, i++, stats->GetVal(wxT("autoanalyze_count")));
			}
			stats->MoveNext();
			pos++;
		}

		delete stats;
	}
	statistics->SetColumnWidth(0,wxLIST_AUTOSIZE);
}


///////////////////////////////////////////////////////////


void pgTable::ShowStatistics(frmMain *form, ctlListView *statistics)
{
	if (GetIsPartitioned()) {
		ShowStatisticsTables(form,statistics,this);
		return;
	}
	wxString sql =
	    wxT("SELECT seq_scan AS ") + qtIdent(_("Sequential Scans")) +
	    wxT(", seq_tup_read AS ") + qtIdent(_("Sequential Tuples Read")) +
	    wxT(", idx_scan AS ") + qtIdent(_("Index Scans")) +
	    wxT(", idx_tup_fetch AS ") + qtIdent(_("Index Tuples Fetched")) +
	    wxT(", n_tup_ins AS ") + qtIdent(_("Tuples Inserted")) +
	    wxT(", n_tup_upd AS ") + qtIdent(_("Tuples Updated")) +
	    wxT(", n_tup_del AS ") + qtIdent(_("Tuples Deleted"));

	if (GetConnection()->BackendMinimumVersion(8, 3))
	{
		sql +=
		    wxT(", n_tup_hot_upd AS ") + qtIdent(_("Tuples HOT Updated")) +
		    wxT(", n_live_tup AS ") + qtIdent(_("Live Tuples")) +
		    wxT(", n_dead_tup AS ") + qtIdent(_("Dead Tuples"));
	}

	sql +=   wxT(", heap_blks_read AS ") + qtIdent(_("Heap Blocks Read")) +
	         wxT(", heap_blks_hit AS ") + qtIdent(_("Heap Blocks Hit")) +
	         wxT(", idx_blks_read AS ") + qtIdent(_("Index Blocks Read")) +
	         wxT(", idx_blks_hit AS ") + qtIdent(_("Index Blocks Hit")) +
	         wxT(", toast_blks_read AS ") + qtIdent(_("Toast Blocks Read")) +
	         wxT(", toast_blks_hit AS ") + qtIdent(_("Toast Blocks Hit")) +
	         wxT(", tidx_blks_read AS ") + qtIdent(_("Toast Index Blocks Read")) +
	         wxT(", tidx_blks_hit AS ") + qtIdent(_("Toast Index Blocks Hit"));

	if (GetConnection()->BackendMinimumVersion(8, 2))
	{
		sql +=
		    wxT(", last_vacuum AS ") + qtIdent(_("Last Vacuum")) +
		    wxT(", last_autovacuum AS ") + qtIdent(_("Last Autovacuum")) +
		    wxT(", last_analyze AS ") + qtIdent(_("Last Analyze")) +
		    wxT(", last_autoanalyze AS ") + qtIdent(_("Last Autoanalyze"));
	}

	if (GetConnection()->BackendMinimumVersion(9, 1))
	{
		sql +=
		    wxT(", vacuum_count AS ") + qtIdent(_("Vacuum counter")) +
		    wxT(", autovacuum_count AS ") + qtIdent(_("Autovacuum counter")) +
		    wxT(", analyze_count AS ") + qtIdent(_("Analyze counter")) +
		    wxT(", autoanalyze_count AS ") + qtIdent(_("Autoanalyze counter"));
	}

	if (GetConnection()->HasFeature(FEATURE_SIZE))
	{
		sql += wxT(", pg_size_pretty(pg_relation_size(stat.relid)) AS ") + qtIdent(_("Table Size"))
		       +  wxT(", CASE WHEN cl.reltoastrelid = 0 THEN ") + qtDbString(_("none")) + wxT(" ELSE pg_size_pretty(pg_relation_size(cl.reltoastrelid)+ COALESCE((SELECT SUM(pg_relation_size(indexrelid)) FROM pg_index WHERE indrelid=cl.reltoastrelid)::int8, 0)) END AS ") + qtIdent(_("Toast Table Size"))
		       +  wxT(", pg_size_pretty(COALESCE((SELECT SUM(pg_relation_size(indexrelid)) FROM pg_index WHERE indrelid=stat.relid)::int8, 0)) AS ") + qtIdent(_("Indexes Size"));
	}

	if (showExtendedStatistics)
	{
		sql += wxT("\n")
		       wxT(", tuple_count AS ") + qtIdent(_("Tuple Count")) + wxT(",\n")
		       wxT("  pg_size_pretty(tuple_len) AS ") + qtIdent(_("Tuple Length")) + wxT(",\n")
		       wxT("  tuple_percent AS ") + qtIdent(_("Tuple Percent")) + wxT(",\n")
		       wxT("  dead_tuple_count AS ") + qtIdent(_("Dead Tuple Count")) + wxT(",\n")
		       wxT("  pg_size_pretty(dead_tuple_len) AS ") + qtIdent(_("Dead Tuple Length")) + wxT(",\n")
		       wxT("  dead_tuple_percent AS ") + qtIdent(_("Dead Tuple Percent")) + wxT(",\n")
		       wxT("  pg_size_pretty(free_space) AS ") + qtIdent(_("Free Space")) + wxT(",\n")
		       wxT("  free_percent AS ") + qtIdent(_("Free Percent")) + wxT("\n")
		       wxT("  FROM pgstattuple('") + GetQuotedFullIdentifier() + wxT("'), pg_stat_all_tables stat");
	}
	else
	{
		sql += wxT("\n")
		       wxT("  FROM pg_stat_all_tables stat");
	}
	sql +=  wxT("\n")
	        wxT("  JOIN pg_statio_all_tables statio ON stat.relid = statio.relid\n")
	        wxT("  JOIN pg_class cl ON cl.oid=stat.relid\n")
	        wxT(" WHERE stat.relid = ") + GetOidStr();


	DisplayStatistics(statistics, sql);
}


pgObject *pgTableFactory::CreateObjects(pgCollection *collection, ctlTree *browser, const wxString &restriction)
{
	wxString query;
	pgTable *table = 0;

	// Greenplum returns reltuples and relpages as tuples per segmentDB and pages per segmentDB,
	// so we need to multiply them by the number of segmentDBs to get reasonable values.
	long gp_segments = 1;
	if (collection->GetConnection()->GetIsGreenplum())
	{
		query = wxT("SELECT count(*) AS gp_segments from pg_catalog.gp_configuration where definedprimary = 't' and content >= 0");
		gp_segments = StrToLong(collection->GetDatabase()->ExecuteScalar(query));
		if (gp_segments <= 1)
			gp_segments = 1;
	}

	pgSet *tables;
	if (collection->GetConnection()->BackendMinimumVersion(8, 0))
	{
		query = wxT("SELECT rel.oid, rel.relname, rel.reltablespace AS spcoid, spc.spcname, pg_get_userbyid(rel.relowner) AS relowner, rel.relacl, ")
		        wxT("rel.relhassubclass, rel.reltuples, des.description, con.conname, con.conkey,\n")
		        wxT("       EXISTS(select 1 FROM pg_trigger\n")
		        wxT("                       JOIN pg_proc pt ON pt.oid=tgfoid AND pt.proname='logtrigger'\n")
		        wxT("                       JOIN pg_proc pc ON pc.pronamespace=pt.pronamespace AND pc.proname='slonyversion'\n")
		        wxT("                     WHERE tgrelid=rel.oid) AS isrepl,\n");

		if (collection->GetConnection()->BackendMinimumVersion(9, 0))
		{
			query += wxT("       (select count(*) FROM pg_trigger\n")
			         wxT("                     WHERE tgrelid=rel.oid AND tgisinternal = FALSE) AS triggercount\n");
		}
		else
		{
			query += wxT("       (select count(*) FROM pg_trigger\n")
			         wxT("                     WHERE tgrelid=rel.oid AND tgisconstraint = FALSE) AS triggercount\n");
		}

		if (collection->GetConnection()->BackendMinimumVersion(9, 1))
			query += wxT(", rel.relpersistence \n");
		if (collection->GetConnection()->BackendMinimumVersion(8, 2))
			query += wxT(", substring(array_to_string(rel.reloptions, ',') FROM 'fillfactor=([0-9]*)') AS fillfactor \n");
		if (collection->GetConnection()->GetIsGreenplum())
		{
			query += wxT(", gpd.localoid, gpd.attrnums \n");
			query += wxT(", substring(array_to_string(rel.reloptions, ',') from 'appendonly=([a-z]*)') AS appendonly \n");
			query += wxT(", substring(array_to_string(rel.reloptions, ',') from 'compresslevel=([0-9]*)') AS compresslevel \n");
			query += wxT(", substring(array_to_string(rel.reloptions, ',') from 'orientation=([a-z]*)') AS orientation \n");
			query += wxT(", substring(array_to_string(rel.reloptions, ',') from 'compresstype=([a-z0-9]*)') AS compresstype \n");
			query += wxT(", substring(array_to_string(rel.reloptions, ',') from 'blocksize=([0-9]*)') AS blocksize \n");
			query += wxT(", substring(array_to_string(rel.reloptions, ',') from 'checksum=([a-z]*)') AS checksum \n");
			if (collection->GetConnection()->GetIsGreenplum() && collection->GetConnection()->BackendMinimumVersion(8, 2, 9))
				query += wxT(", rel.oid in (select parrelid from pg_partition) as ispartitioned\n");
		}
		else if (collection->GetConnection()->BackendMinimumVersion(8, 4))
		{
			query += wxT(", substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS autovacuum_enabled \n")
			         wxT(", substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS autovacuum_vacuum_threshold \n")
			         wxT(", substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9.]*)') AS autovacuum_vacuum_scale_factor \n")
			         wxT(", substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS autovacuum_analyze_threshold \n")
			         wxT(", substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9.]*)') AS autovacuum_analyze_scale_factor \n")
			         wxT(", substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS autovacuum_vacuum_cost_delay \n")
			         wxT(", substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS autovacuum_vacuum_cost_limit \n")
			         wxT(", substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS autovacuum_freeze_min_age \n")
			         wxT(", substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS autovacuum_freeze_max_age \n")
			         wxT(", substring(array_to_string(rel.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS autovacuum_freeze_table_age \n")
			         wxT(", substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_enabled=([a-z|0-9]*)') AS toast_autovacuum_enabled \n")
			         wxT(", substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_threshold=([0-9]*)') AS toast_autovacuum_vacuum_threshold \n")
			         wxT(", substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_scale_factor=([0-9.]*)') AS toast_autovacuum_vacuum_scale_factor \n")
			         wxT(", substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_threshold=([0-9]*)') AS toast_autovacuum_analyze_threshold \n")
			         wxT(", substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_analyze_scale_factor=([0-9.]*)') AS toast_autovacuum_analyze_scale_factor \n")
			         wxT(", substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_delay=([0-9]*)') AS toast_autovacuum_vacuum_cost_delay \n")
			         wxT(", substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_vacuum_cost_limit=([0-9]*)') AS toast_autovacuum_vacuum_cost_limit \n")
			         wxT(", substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_min_age=([0-9]*)') AS toast_autovacuum_freeze_min_age \n")
			         wxT(", substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_max_age=([0-9]*)') AS toast_autovacuum_freeze_max_age \n")
			         wxT(", substring(array_to_string(tst.reloptions, ',') FROM 'autovacuum_freeze_table_age=([0-9]*)') AS toast_autovacuum_freeze_table_age \n")
			         wxT(", rel.reloptions AS reloptions, tst.reloptions AS toast_reloptions \n")
			         wxT(", (CASE WHEN rel.reltoastrelid = 0 THEN false ELSE true END) AS hastoasttable\n");
		}
		if (collection->GetConnection()->BackendMinimumVersion(9, 0))
			query += wxT(", rel.reloftype, typ.typname\n");
		if (collection->GetConnection()->HasFeature(FEATURE_TRACK_COMMIT_TS))
			query += wxT(", pg_xact_commit_timestamp(typ2.xmin) create_ts\n");
		if (collection->GetDatabase()->BackendMinimumVersion(9, 1))
		{
			query += wxT(",\n(SELECT array_agg(label) FROM pg_seclabels sl1 WHERE sl1.objoid=rel.oid AND sl1.objsubid=0) AS labels");
			query += wxT(",\n(SELECT array_agg(provider) FROM pg_seclabels sl2 WHERE sl2.objoid=rel.oid AND sl2.objsubid=0) AS providers");
		}
		wxString pg10=wxEmptyString;
		if (collection->GetDatabase()->BackendMinimumVersion(10, 0))
		{
			query += wxT(",case when lk.relation=rel.oid then null else pg_get_partkeydef(rel.oid) end \n AS partkeydef");
			query += wxT(",case when lk.relation=rel.oid then null else pg_get_expr(rel.relpartbound, rel.oid) end \n AS partexp");
			query += wxT(",(select count(*)from pg_inherits ii where ii.inhparent=rel.oid) >0 AS ispartitioned");
			//pg10=wxT("pg_get_expr(rel.relpartbound, rel.oid) is null and");
			pg10=wxT("(rel.relpartbound is null and i.inhrelid is null) and");
			//query += wxT(",\n(SELECT array_agg(provider) FROM pg_seclabels sl2 WHERE sl2.objoid=rel.oid AND sl2.objsubid=0) AS providers");
		}
//		'ALTER STATISTICS '||substring('CREATE STATISTICS public.tab_a (dependencies) ON c1, c3 FROM a' from 'ICS (.+?)\s\(')||' OWNER TO '||
		//select relation from pg_locks where locktype='relation' and granted=true and mode='AccessExclusiveLock'
		if (collection->GetDatabase()->connection()->GetIsPgProEnt()) query += wxT(",left((cfs_fragmentation(rel.oid)*100)::text,5)::text AS cfs_ratio");
			else query += wxT(",null::text AS cfs_ratio");

		if (collection->GetDatabase()->BackendMinimumVersion(10, 0))
		{
			//query += wxT(",\n pg_get_statisticsobjdef(stat_ext.oid) AS stat_stmt");
			//query += wxT(",\nCASE WHEN stat_ext.stxowner<>rel.relowner then 'ALTER STATISTICS '||substring(pg_get_statisticsobjdef(stat_ext.oid) from 'ICS (.+?)\\s\\(')||' OWNER TO '||stat_ext.stxowner::regrole else null end AS alter_stmt");
			query += ",(select string_agg(pg_get_statisticsobjdef(stat_ext.oid)||CASE WHEN stat_ext.stxowner<>rl.relowner then E';\\nALTER STATISTICS '||substring(pg_get_statisticsobjdef(stat_ext.oid) from 'ICS (.+?)\\s')||' OWNER TO '||stat_ext.stxowner::regrole else '' end"
				;
			if (collection->GetDatabase()->BackendMinimumVersion(13, 0)) {
				query += "||CASE WHEN stat_ext.stxstattarget<>-1  then E';\\nALTER STATISTICS '||substring(pg_get_statisticsobjdef(stat_ext.oid) from 'ICS (.+?)\\s')||' SET STATISTICS '||stat_ext.stxstattarget else '' end";
			}
			query += ",E';\\n' order by stat_ext.stxrelid) stat_stmt from pg_class rl join  pg_statistic_ext stat_ext on rl.oid=stat_ext.stxrelid where stat_ext.stxrelid=rel.oid) stat_stmt";
		}
		query += wxT("  FROM pg_class rel\n")
				 wxT("  LEFT JOIN  pg_locks lk ON locktype='relation' and granted=true and mode='AccessExclusiveLock' and relation=rel.oid\n")
		         wxT("  LEFT OUTER JOIN pg_tablespace spc on spc.oid=rel.reltablespace\n")
		         wxT("  LEFT OUTER JOIN pg_description des ON (des.objoid=rel.oid AND des.objsubid=0 AND des.classoid='pg_class'::regclass)\n")
		         wxT("  LEFT OUTER JOIN pg_inherits i on i.inhrelid=rel.oid\n")
		         wxT("  LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid AND con.contype='p'\n");

		// Add the toast table for vacuum parameters.
		if (collection->GetConnection()->BackendMinimumVersion(8, 4))
			query += wxT("  LEFT OUTER JOIN pg_class tst ON tst.oid = rel.reltoastrelid\n");

		if (collection->GetConnection()->GetIsGreenplum())
			query += wxT("  LEFT OUTER JOIN gp_distribution_policy gpd ON gpd.localoid=rel.oid\n");

		if (collection->GetConnection()->BackendMinimumVersion(9, 0))
			query += wxT("LEFT JOIN pg_type typ ON rel.reloftype=typ.oid\n");
		if (collection->GetConnection()->HasFeature(FEATURE_TRACK_COMMIT_TS))
			query += wxT("LEFT JOIN pg_type typ2 ON rel.oid=typ2.typrelid\n");

		query += wxT(" WHERE ")+pg10+wxT(" rel.relkind IN ('r','s','t','p') AND rel.relnamespace = ") + collection->GetSchema()->GetOidStr() + wxT("\n");

		// Greenplum: Eliminate (sub)partitions from the display, only show the parent partitioned table
		// and eliminate external tables
		if (collection->GetConnection()->GetIsGreenplum() && collection->GetConnection()->BackendMinimumVersion(8, 2, 9))
			query += wxT("AND rel.relstorage <> 'x' AND rel.oid NOT IN (SELECT parchildrelid from pg_partition_rule)");
		// only show the parent partitioned table
		if (collection->GetConnection()->BackendMinimumVersion(10, 0)){
			//addIcon(tables_png_img);
			query += wxT("AND (not (rel.relkind='r' and rel.relpartbound IS NOT NULL)) ");
		}

		query += restriction +
		         wxT(" ORDER BY rel.relname");
	}
	else
	{
		query = wxT("SELECT rel.oid, rel.relname, pg_get_userbyid(rel.relowner) AS relowner, rel.relacl, ")
		        wxT("rel.relhassubclass, rel.reltuples, des.description, con.conname, con.conkey,\n")
		        wxT("       (select count(*) FROM pg_trigger\n")
		        wxT("                     WHERE tgrelid=rel.oid AND tgisconstraint = FALSE) AS triggercount,\n")
		        wxT("       EXISTS(select 1 FROM pg_trigger\n")
		        wxT("                       JOIN pg_proc pt ON pt.oid=tgfoid AND pt.proname='logtrigger'\n")
		        wxT("                       JOIN pg_proc pc ON pc.pronamespace=pt.pronamespace AND pc.proname='slonyversion'\n")
		        wxT("                     WHERE tgrelid=rel.oid) AS isrepl\n")
		        wxT("  FROM pg_class rel\n")
		        wxT("  LEFT OUTER JOIN pg_description des ON (des.objoid=rel.oid AND des.objsubid=0 AND des.classoid='pg_class'::regclass)\n")
		        wxT("  LEFT OUTER JOIN pg_constraint con ON con.conrelid=rel.oid AND con.contype='p'\n")
		        wxT(" WHERE rel.relkind IN ('r','s','t') AND rel.relnamespace = ") + collection->GetSchema()->GetOidStr() + wxT("\n")
		        + restriction +
		        wxT(" ORDER BY rel.relname");
	}
	tables = collection->GetDatabase()->ExecuteSet(query);
	if (tables)
	{
		while (!tables->Eof())
		{
			table = new pgTable(collection->GetSchema(), tables->GetVal(wxT("relname")));

			table->iSetOid(tables->GetOid(wxT("oid")));
			table->iSetOwner(tables->GetVal(wxT("relowner")));
			table->iSetAcl(tables->GetVal(wxT("relacl")));
			table->iSetRatio(tables->GetVal(wxT("cfs_ratio")));
			if (collection->GetConnection()->HasFeature(FEATURE_TRACK_COMMIT_TS)) {
				table->iSetCreateTableTS(tables->GetVal(wxT("create_ts")));
			}
			if (collection->GetConnection()->BackendMinimumVersion(8, 0))
			{
				if (tables->GetOid(wxT("spcoid")) == 0)
					table->iSetTablespaceOid(collection->GetDatabase()->GetTablespaceOid());
				else
					table->iSetTablespaceOid(tables->GetOid(wxT("spcoid")));

				if (tables->GetVal(wxT("spcname")) == wxEmptyString)
					table->iSetTablespace(collection->GetDatabase()->GetTablespace());
				else
					table->iSetTablespace(tables->GetVal(wxT("spcname")));
			}
			
			if (collection->GetConnection()->BackendMinimumVersion(9, 0))
			{
				table->iSetOfTypeOid(tables->GetOid(wxT("reloftype")));
				table->iSetOfType(tables->GetVal(wxT("typname")));
			}
			else
			{
				table->iSetOfTypeOid(0);
				table->iSetOfType(wxT(""));
			}
			table->iSetComment(tables->GetVal(wxT("description")));
			if (collection->GetConnection()->BackendMinimumVersion(9, 1))
				table->iSetUnlogged(tables->GetVal(wxT("relpersistence")) == wxT("u"));
			else
				table->iSetUnlogged(false);
			table->iSetHasOids(false);
			table->iSetEstimatedRows(tables->GetDouble(wxT("reltuples")) * gp_segments);
			if (collection->GetConnection()->BackendMinimumVersion(8, 2))
			{
				table->iSetFillFactor(tables->GetVal(wxT("fillfactor")));
			}
			
			if (collection->GetConnection()->BackendMinimumVersion(8, 4))
			{
				table->iSetRelOptions(tables->GetVal(wxT("reloptions")));
				if (table->GetCustomAutoVacuumEnabled())
				{
					if (tables->GetVal(wxT("autovacuum_enabled")).IsEmpty())
						table->iSetAutoVacuumEnabled(2);
					else if (tables->GetBool(wxT("autovacuum_enabled")))
						table->iSetAutoVacuumEnabled(1);
					else
						table->iSetAutoVacuumEnabled(0);
					table->iSetAutoVacuumVacuumThreshold(tables->GetVal(wxT("autovacuum_vacuum_threshold")));
					table->iSetAutoVacuumVacuumScaleFactor(tables->GetVal(wxT("autovacuum_vacuum_scale_factor")));
					table->iSetAutoVacuumAnalyzeThreshold(tables->GetVal(wxT("autovacuum_analyze_threshold")));
					table->iSetAutoVacuumAnalyzeScaleFactor(tables->GetVal(wxT("autovacuum_analyze_scale_factor")));
					table->iSetAutoVacuumVacuumCostDelay(tables->GetVal(wxT("autovacuum_vacuum_cost_delay")));
					table->iSetAutoVacuumVacuumCostLimit(tables->GetVal(wxT("autovacuum_vacuum_cost_limit")));
					table->iSetAutoVacuumFreezeMinAge(tables->GetVal(wxT("autovacuum_freeze_min_age")));
					table->iSetAutoVacuumFreezeMaxAge(tables->GetVal(wxT("autovacuum_freeze_max_age")));
					table->iSetAutoVacuumFreezeTableAge(tables->GetVal(wxT("autovacuum_freeze_table_age")));
				}
				table->iSetHasToastTable(tables->GetBool(wxT("hastoasttable")));
				if (table->GetHasToastTable())
				{
					table->iSetToastRelOptions(tables->GetVal(wxT("toast_reloptions")));

					if (table->GetToastCustomAutoVacuumEnabled())
					{
						if (tables->GetVal(wxT("toast_autovacuum_enabled")).IsEmpty())
							table->iSetToastAutoVacuumEnabled(2);
						else if (tables->GetBool(wxT("toast_autovacuum_enabled")))
							table->iSetToastAutoVacuumEnabled(1);
						else
							table->iSetToastAutoVacuumEnabled(0);

						table->iSetToastAutoVacuumVacuumThreshold(tables->GetVal(wxT("toast_autovacuum_vacuum_threshold")));
						table->iSetToastAutoVacuumVacuumScaleFactor(tables->GetVal(wxT("toast_autovacuum_vacuum_scale_factor")));
						table->iSetToastAutoVacuumVacuumCostDelay(tables->GetVal(wxT("toast_autovacuum_vacuum_cost_delay")));
						table->iSetToastAutoVacuumVacuumCostLimit(tables->GetVal(wxT("toast_autovacuum_vacuum_cost_limit")));
						table->iSetToastAutoVacuumFreezeMinAge(tables->GetVal(wxT("toast_autovacuum_freeze_min_age")));
						table->iSetToastAutoVacuumFreezeMaxAge(tables->GetVal(wxT("toast_autovacuum_freeze_max_age")));
						table->iSetToastAutoVacuumFreezeTableAge(tables->GetVal(wxT("toast_autovacuum_freeze_table_age")));
					}
				}
			}
			table->iSetHasSubclass(tables->GetBool(wxT("relhassubclass")));
			table->iSetPrimaryKeyName(tables->GetVal(wxT("conname")));
			table->iSetIsReplicated(tables->GetBool(wxT("isrepl")));
			table->iSetTriggerCount(tables->GetLong(wxT("triggercount")));
			wxString cn = tables->GetVal(wxT("conkey"));
			cn = cn.Mid(1, cn.Length() - 2);
			table->iSetPrimaryKeyColNumbers(cn);
			table->iSetPartitionDef(wxT(""));
			table->iSetIsPartitioned(false);

			if (collection->GetConnection()->BackendMinimumVersion(10, 0))
			{
				table->iSetPartKeyDef(tables->GetVal(wxT("partkeydef")));
				table->iSetPartExp(tables->GetVal(wxT("partexp")));
				table->iSetIsPartitioned(tables->GetBool(wxT("ispartitioned")));
				wxString st = tables->GetVal(wxT("stat_stmt"));
				//wxString at = tables->GetVal(wxT("alter_stmt"));
				if (!st.IsEmpty()) if ((st.Right(1) != ";") ) st=st+wxT(";\n");
				table->iSetStatExt(st);

			}
			if (collection->GetConnection()->GetIsGreenplum())
			{
				Oid lo = tables->GetOid(wxT("localoid"));
				wxString db = tables->GetVal(wxT("attrnums"));
				db = db.Mid(1, db.Length() - 2);
				table->iSetDistributionColNumbers(db);
				if (lo > 0 && db.Length() == 0)
					table->iSetDistributionIsRandom();
				table->iSetAppendOnly(tables->GetVal(wxT("appendonly")));
				table->iSetCompressLevel(tables->GetVal(wxT("compresslevel")));
				table->iSetOrientation(tables->GetVal(wxT("orientation")));
				table->iSetCompressType(tables->GetVal(wxT("compresstype")));
				table->iSetBlocksize(tables->GetVal(wxT("blocksize")));
				table->iSetChecksum(tables->GetVal(wxT("checksum")));


				if (collection->GetConnection()->BackendMinimumVersion(8, 2, 9))
				{
					table->iSetIsPartitioned(tables->GetBool(wxT("ispartitioned")));
				}

			}

			if (collection->GetConnection()->BackendMinimumVersion(9, 1))
			{
				table->iSetProviders(tables->GetVal(wxT("providers")));
				table->iSetLabels(tables->GetVal(wxT("labels")));
			}

			if (browser)
			{
				browser->AppendObject(collection, table);
				tables->MoveNext();
			}
			else
				break;
		}

		delete tables;
	}
	return table;
}

bool pgTableObjCollection::CanCreate()
{
	// We don't create sub-objects of Views or External tables
	if (GetTable()->GetMetaType() == PGM_VIEW || GetTable()->GetMetaType() == GP_EXTTABLE)
		return false;

	return GetSchema()->GetCreatePrivilege();
}


#include "images/table.pngc"
#include "images/table-repl.pngc"
#include "images/table-repl-sm.pngc"
#include "images/table-sm.pngc"
#include "images/tables.pngc"

pgTableFactory::pgTableFactory()
	: pgSchemaObjFactory(__("Table"), __("New Table..."), __("Create a new Table."), table_png_img, table_sm_png_img)
{
	metaType = PGM_TABLE;
	if (WantSmallIcon())
		replicatedIconId = addIcon(table_repl_sm_png_img);
	else
		replicatedIconId = addIcon(table_repl_png_img);
	partitionsIconId=addIcon(tables_png_img);
}

pgCollection *pgTableFactory::CreateCollection(pgObject *obj)
{
	return new pgTableCollection(GetCollectionFactory(), (pgSchema *)obj);
}

pgTableFactory tableFactory;
static pgaCollectionFactory ctf(&tableFactory, __("Tables"), tables_png_img);


pgCollection *pgTableObjFactory::CreateCollection(pgObject *obj)
{
	return new pgTableObjCollection(GetCollectionFactory(), (pgTable *)obj);
}


countRowsFactory::countRowsFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar) : contextActionFactory(list)
{
	mnu->Append(id, _("&Count"), _("Count rows in the selected object."));
}


wxWindow *countRowsFactory::StartDialog(frmMain *form, pgObject *obj)
{
	form->StartMsg(_("Counting rows"));

	((pgTable *)obj)->UpdateRows();

	wxTreeItemId item = form->GetBrowser()->GetSelection();
	if (obj == form->GetBrowser()->GetObject(item))
		obj->ShowTreeDetail(form->GetBrowser(), 0, form->GetProperties());

	form->EndMsg();

	return 0;
}


bool countRowsFactory::CheckEnable(pgObject *obj)
{
	return obj && ( obj->IsCreatedBy(pg_partitionFactory) ||obj->IsCreatedBy(tableFactory));
}


executePgstattupleFactory::executePgstattupleFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar) : contextActionFactory(list)
{
	mnu->Append(id, _("&Extended table statistics"), _("Get extended statistics via pgstattuple for the selected object."), wxITEM_CHECK);
}


wxWindow *executePgstattupleFactory::StartDialog(frmMain *form, pgObject *obj)
{
	if (!((pgTable *)obj)->GetShowExtendedStatistics())
	{
		((pgTable *)obj)->iSetShowExtendedStatistics(true);
		wxTreeItemId item = form->GetBrowser()->GetSelection();
		if (obj == form->GetBrowser()->GetObject(item))
			form->SelectStatisticsTab();
	}
	else
		((pgTable *)obj)->iSetShowExtendedStatistics(false);

	form->GetMenuFactories()->CheckMenu(obj, form->GetMenuBar(), (ctlMenuToolbar *)form->GetToolBar());

	return 0;
}


bool executePgstattupleFactory::CheckEnable(pgObject *obj)
{
	return obj && ( obj->IsCreatedBy(pg_partitionFactory) ||obj->IsCreatedBy(tableFactory)) && ((pgTable *)obj)->HasPgstattuple();
}

bool executePgstattupleFactory::CheckChecked(pgObject *obj)
{
	return obj && ( obj->IsCreatedBy(pg_partitionFactory) ||obj->IsCreatedBy(tableFactory)) && ((pgTable *)obj)->GetShowExtendedStatistics();
}

disableAllTriggersFactory::disableAllTriggersFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar) : contextActionFactory(list)
{
	mnu->Append(id, _("Disable triggers"), _("Disable all triggers on the selected table."));
}


wxWindow *disableAllTriggersFactory::StartDialog(frmMain *form, pgObject *obj)
{
	if (wxMessageBox(_("Are you sure you wish to disable all triggers on this table?"), _("Disable triggers"), wxYES_NO) != wxYES)
		return 0;

	if (!((pgTable *)obj)->EnableTriggers(false))
		return 0;

	((pgTable *)obj)->iSetTriggersEnabled(form->GetBrowser(), false);

	return 0;
}


bool disableAllTriggersFactory::CheckEnable(pgObject *obj)
{
	return obj && ( obj->IsCreatedBy(pg_partitionFactory) ||obj->IsCreatedBy(tableFactory)) && obj->CanEdit()
	       && (obj->GetOwner() == obj->GetConnection()->GetUser() || obj->GetServer()->GetSuperUser())
	       && ((pgTable *)obj)->GetConnection()->BackendMinimumVersion(8, 1)
	       && ((pgTable *)obj)->GetTriggerCount() > 0;
}

enableAllTriggersFactory::enableAllTriggersFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar) : contextActionFactory(list)
{
	mnu->Append(id, _("Enable triggers"), _("Enable all triggers on the selected table."));
}


wxWindow *enableAllTriggersFactory::StartDialog(frmMain *form, pgObject *obj)
{
	if (wxMessageBox(_("Are you sure you wish to enable all triggers on this table?"), _("Enable triggers"), wxYES_NO) != wxYES)
		return 0;

	if (!((pgTable *)obj)->EnableTriggers(true))
		return 0;

	((pgTable *)obj)->iSetTriggersEnabled(form->GetBrowser(), true);

	return 0;
}


bool enableAllTriggersFactory::CheckEnable(pgObject *obj)
{
	return obj && ( obj->IsCreatedBy(pg_partitionFactory) ||obj->IsCreatedBy(tableFactory)) && obj->CanEdit()
	       && (obj->GetOwner() == obj->GetConnection()->GetUser() || obj->GetServer()->GetSuperUser())
	       && ((pgTable *)obj)->GetConnection()->BackendMinimumVersion(8, 1)
	       && ((pgTable *)obj)->GetTriggerCount() > 0;
}

truncateFactory::truncateFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar) : contextActionFactory(list)
{
	mnu->Append(id, _("&Truncate"),  _("Truncate the selected table."));
}


wxWindow *truncateFactory::StartDialog(frmMain *form, pgObject *obj)
{
	if (wxMessageBox(_("Are you sure you wish to truncate this table?\n\nWARNING: This action will delete ALL data in the table!"), _("Truncate table"), wxYES_NO | wxICON_QUESTION | wxNO_DEFAULT) != wxYES)
		return 0;

	((pgTable *)obj)->Truncate(false);
	((pgTable *)obj)->UpdateRows();
	wxTreeItemId item = form->GetBrowser()->GetSelection();
	if (obj == form->GetBrowser()->GetObject(item))
		obj->ShowTreeDetail(form->GetBrowser(), 0, form->GetProperties());

	return 0;
}


bool truncateFactory::CheckEnable(pgObject *obj)
{
	return obj && obj->IsCreatedBy(tableFactory);
}


truncateCascadedFactory::truncateCascadedFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar) : contextActionFactory(list)
{
	mnu->Append(id, _("Truncate Cascaded"), _("Truncate the selected table and all referencing tables."));
}


wxWindow *truncateCascadedFactory::StartDialog(frmMain *form, pgObject *obj)
{
	if (wxMessageBox(_("Are you sure you wish to truncate this table and all tables that have foreign key references to this table?\n\nWARNING: This action will delete ALL data in the tables!"), _("Truncate table cascaded"), wxYES_NO | wxICON_QUESTION | wxNO_DEFAULT) != wxYES)
		return 0;

	((pgTable *)obj)->Truncate(true);
	((pgTable *)obj)->UpdateRows();
	wxTreeItemId item = form->GetBrowser()->GetSelection();
	if (obj == form->GetBrowser()->GetObject(item))
		obj->ShowTreeDetail(form->GetBrowser(), 0, form->GetProperties());

	return 0;
}


bool truncateCascadedFactory::CheckEnable(pgObject *obj)
{
	return obj && obj->IsCreatedBy(tableFactory) && ((pgTable *)obj)->GetConnection()->BackendMinimumVersion(8, 2);
}


resetTableStatsFactory::resetTableStatsFactory(menuFactoryList *list, wxMenu *mnu, ctlMenuToolbar *toolbar) : contextActionFactory(list)
{
	mnu->Append(id, _("&Reset table statistics"),  _("Reset statistics of the selected table."));
}


wxWindow *resetTableStatsFactory::StartDialog(frmMain *form, pgObject *obj)
{
	if (wxMessageBox(_("Are you sure you wish to reset statistics of this table?"), _("Reset statistics"), wxYES_NO) != wxYES)
		return 0;

	((pgTable *)obj)->ResetStats();
	((pgTable *)obj)->ShowStatistics(form, form->GetStatistics());

	return 0;
}


bool resetTableStatsFactory::CheckEnable(pgObject *obj)
{
	return obj && ( obj->IsCreatedBy(pg_partitionFactory) ||obj->IsCreatedBy(tableFactory)) && ((pgTable *)obj)->GetConnection()->BackendMinimumVersion(9, 0);
}
