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

// wxWindows headers
#include <wx/wx.h>

// App headers
#include "pgAdmin3.h"
#include "utils/misc.h"
#include "utils/pgDefs.h"
#include "schema/pgDatatype.h"
#include "schema/pgColumn.h"


pgColumn::pgColumn(pgTable *newTable, const wxString &newName)
	: pgTableObject(newTable, columnFactory, newName)
{
	isFK = false;
	isPK = false;
	isReferenced = -1;
}

pgColumn::~pgColumn()
{
}


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

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

	return message;
}


bool pgColumn::IsReferenced()
{
	if (isReferenced < 0)
	{
		isReferenced = (int)StrToLong(GetConnection()->ExecuteScalar(
		                                  wxT("SELECT COUNT(1) FROM pg_depend dep\n")
		                                  wxT("  JOIN pg_class cl ON dep.classid=cl.oid AND relname='pg_rewrite'\n")
		                                  wxT(" WHERE refobjid=") + GetTableOidStr()
		                                  + wxT(" AND refobjsubid=") + NumToStr(GetColNumber())));
	}

	return (isReferenced != 0);
}


bool pgColumn::DropObject(wxFrame *frame, ctlTree *browser, bool cascaded)
{
	wxString sql = wxT("ALTER TABLE ") + GetQuotedFullTable();
	sql += wxT(" DROP COLUMN ") + GetQuotedIdentifier();

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


void pgColumn::ShowDependencies(frmMain *form, ctlListView *Dependencies, const wxString &where)
{
	pgObject::ShowDependencies(form, Dependencies,
	                           wxT("\n WHERE dep.objid=") + table->GetOidStr() +
	                           wxT(" AND dep.objsubid=") + NumToStr(colNumber));
}


void pgColumn::ShowDependents(frmMain *form, ctlListView *referencedBy, const wxString &where)
{
	pgObject::ShowDependents(form, referencedBy,
	                         wxT("\n WHERE dep.refobjid=") + table->GetOidStr() +
	                         wxT(" AND dep.refobjsubid=") + NumToStr(colNumber));
}

wxString pgColumn::GetSql(ctlTree *browser)
{
	if (sql.IsNull() && !GetSystemObject())
	{
		if (GetTable()->GetMetaType() == PGM_VIEW)
		{
			sql = wxT("-- Column: ") + GetQuotedIdentifier() + wxT("\n\n");

			if (!GetDefault().IsEmpty())
				sql += wxT("ALTER TABLE ") + GetQuotedFullTable()
				       + wxT(" ALTER COLUMN ") + GetQuotedIdentifier()
				       + wxT(" SET DEFAULT ") + GetDefault() + wxT(";\n");

			sql += GetCommentSql();
		}
		else if (GetTable()->GetMetaType() == PGM_CATALOGOBJECT || GetTable()->GetMetaType() == GP_EXTTABLE)
		{
			sql = wxT("-- Column: ") + GetQuotedIdentifier() + wxT("\n\n");
		}
		else
		{
			if (GetInheritedCount())
				sql = wxT("-- Column inherited; cannot be changed");
			else
			{
				sql = wxT("-- Column: ") + GetQuotedIdentifier() + wxT("\n\n")
				      + wxT("-- ALTER TABLE ") + GetQuotedFullTable()
				      + wxT(" DROP COLUMN ") + GetQuotedIdentifier() + wxT(";")

				      + wxT("\n\nALTER TABLE ") + GetQuotedFullTable()
				      + wxT(" ADD COLUMN ") + GetQuotedIdentifier() + wxT(" ")
				      + GetQuotedTypename();
				if (!GetCollation().IsEmpty() && GetCollation() != wxT("pg_catalog.\"default\""))
					sql += wxT(" COLLATE ") + GetCollation();
				sql += wxT(";\n");

				sql += GetStorageSql();

				if (GetNotNull())
					sql += wxT("ALTER TABLE ") + GetQuotedFullTable()
					       + wxT(" ALTER COLUMN ") + GetQuotedIdentifier()
					       + wxT(" SET NOT NULL;\n");
				if (!GetDefault().IsEmpty())
					sql += wxT("ALTER TABLE ") + GetQuotedFullTable()
					       + wxT(" ALTER COLUMN ") + GetQuotedIdentifier()
					       + wxT(" SET DEFAULT ") + GetDefault() + wxT(";\n");
				sql += GetAttstattargetSql();

				sql += GetVariablesSql();

				sql += GetCommentSql();

				if (GetDatabase()->BackendMinimumVersion(8, 4))
					sql += GetPrivileges();

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

	return sql;
}

wxString pgColumn::GetCommentSql()
{
	wxString commentSql;

	if (!GetComment().IsEmpty())
		commentSql = wxT("COMMENT ON COLUMN ") + GetQuotedFullTable() + wxT(".") + GetQuotedIdentifier()
		             +  wxT(" IS ") + qtDbString(GetComment()) + wxT(";\n");

	return commentSql;
}

wxString pgColumn::GetStorageSql()
{
	wxString storageSql;

	if (GetStorage() != GetDefaultStorage())
		storageSql = wxT("ALTER TABLE ") + GetQuotedFullTable()
		             + wxT(" ALTER COLUMN ") + GetQuotedIdentifier()
		             + wxT(" SET STORAGE ") + GetStorage() + wxT(";\n");

	return storageSql;
}

wxString pgColumn::GetAttstattargetSql()
{
	wxString attstattargetSql;

	if (GetAttstattarget() >= 0)
		attstattargetSql = wxT("ALTER TABLE ") + GetQuotedFullTable()
		                   + wxT(" ALTER COLUMN ") + GetQuotedIdentifier()
		                   + wxT(" SET STATISTICS ") + NumToStr(GetAttstattarget()) + wxT(";\n");

	return attstattargetSql;
}

wxString pgColumn::GetVariablesSql()
{
	wxString variablesSql;

	size_t i;
	for (i = 0 ; i < variables.GetCount() ; i++)
		variablesSql += wxT("ALTER TABLE ") + GetQuotedFullTable()
		                + wxT(" ALTER COLUMN ") + GetQuotedIdentifier()
		                + wxT(" SET (") + variables.Item(i) + wxT(");\n");

	return variablesSql;
}

wxString pgColumn::GetPrivileges()
{
	wxString privileges;
	wxString strAcl = GetAcl();
	if (!strAcl.IsEmpty())
	{
		wxArrayString aclArray;
		strAcl = strAcl.Mid(1, strAcl.Length() - 2);
		getArrayFromCommaSeparatedList(strAcl, aclArray);
		wxString role;
		for (unsigned int index = 0; index < aclArray.Count(); index++)
		{
			wxString strCurrAcl = aclArray[index];
			/*
			* In rare case, we can have ',' (comma) in the user name.
			* But, we need to handle them also
			*/
			if (strCurrAcl.Find(wxChar('=')) == wxNOT_FOUND)
			{
				// Check it is start of the ACL
				if (strCurrAcl[0U] == (wxChar)'"')
					role = strCurrAcl + wxT(",");
				continue;
			}
			else
				strCurrAcl = role + strCurrAcl;

			if (strCurrAcl[0U] == (wxChar)'"')
				strCurrAcl = strCurrAcl.Mid(1, strCurrAcl.Length() - 1);
			role = strCurrAcl.BeforeLast('=');
			wxString value = strCurrAcl.Mid(role.Length() + 1).BeforeLast('/');

			if (role.Left(6).IsSameAs(wxT("group "), false))
			{
				role = wxT("group ") + qtIdent(qtStrip(role.Mid(6)));
			}
			else if (role.IsEmpty())
			{
				role = wxT("public");
			}
			else
				role = qtIdent(qtStrip(role));

			privileges += pgObject::GetPrivileges(wxT("awrx"), value, GetQuotedFullTable(), role, GetQuotedIdentifier());
			role.Clear();
		}
	}
	return privileges;
}
wxString pgColumn::GetDefinition()
{
	wxString sql = wxEmptyString;
	wxString seqDefault1, seqDefault2;

	if (table->GetOfTypeOid() == 0)
		sql += GetQuotedTypename();

	if (GetDatabase()->BackendMinimumVersion(16, 0))
	{
		if (GetStorage() != GetDefaultStorage())
			sql += " STORAGE " + GetStorage();
	}
	if (GetDatabase()->BackendMinimumVersion(14, 0))
	{
		wxString cmp = GetCompression();
		if (!cmp.IsEmpty())
			sql += " COMPRESSION " + GetCompression();
	}

	if (!GetCollation().IsEmpty() && GetCollation() != wxT("pg_catalog.\"default\""))
		sql += wxT(" COLLATE ") + GetCollation();

	wxString full_tabname = wxEmptyString;
	if (GetDatabase()->BackendMinimumVersion(8, 1))
	{
		wxString schpref;

		schpref = schema->GetQuotedPrefix();
		full_tabname = GetTableName() + wxT("_") + GetName() + wxT("_seq");

		// If the generated sequence name is longer than 64 characters, then
		//   - If both table & column name exceed 29 chars, truncate both to 29
		//   - If one of table or column name exceeds 29 chars, truncate it to
		//     29 chars plus however much less than 29 chars the other name is.
		if (full_tabname.Length() > 64)
		{
			int tlen = GetTableName().Length();
			int clen = GetName().Length();

			if (tlen > 29 && clen > 29)
				full_tabname = GetTableName().Left(29) + wxT("_") + GetName().Left(29) + wxT("_seq");
			else if (tlen > 29)
				full_tabname = GetTableName().Left(29 + (29 - clen)) + wxT("_") + GetName() + wxT("_seq");
			else if (clen > 29)
				full_tabname = GetTableName() + wxT("_") + GetName().Left(29 + (29 - tlen)) + wxT("_seq");
		}

		full_tabname = qtIdent(full_tabname);

		if (schpref != wxEmptyString)
			full_tabname = schpref + full_tabname;

		seqDefault1 = wxT("nextval('") + full_tabname + wxT("'::regclass)");
		seqDefault2 = seqDefault1;
	}
	else
	{
		seqDefault1 = wxT("nextval('")
		              + schema->GetName() + wxT(".") + GetTableName()
		              + wxT("_") + GetName() + wxT("_seq'::text)");
		seqDefault2 = wxT("nextval('\"")
		              + schema->GetName() + wxT(".") + GetTableName()
		              + wxT("_") + GetName() + wxT("_seq\"'::text)");
	}

	if ((sql == wxT("integer") || sql == wxT("bigint") || sql == wxT("smallint") ||
	        sql == wxT("pg_catalog.integer") || sql == wxT("pg_catalog.bigint") || sql == wxT("pg_catalog.smallint"))
	        && (GetDefault() == seqDefault1 || GetDefault() == seqDefault2))
	{
		if (GetDatabase()->BackendMinimumVersion(8, 1))
		{
			pgSet *set = ExecuteSet(
			                 wxT("SELECT classid\n")
			                 wxT("  FROM pg_depend\n")
			                 wxT(" WHERE refobjid=") + table->GetOidStr() +
			                 wxT(" AND refobjsubid = ") + NumToStr(GetColNumber()) +
			                 wxT(" AND objid = '") + full_tabname + wxT("'::regclass") +
			                 wxT(" AND deptype='a'"));

			if (set && set->NumRows())
			{
				if (sql.Right(6) == wxT("bigint"))
					sql = wxT("bigserial");
				else if (sql.Right(8) == wxT("smallint"))
					sql = wxT("smallserial");
				else
					sql = wxT("serial");
			}

			if (GetNotNull())
				sql += wxT(" NOT NULL");

			if (!set || !(set->NumRows()))
				AppendIfFilled(sql, wxT(" DEFAULT "), GetDefault());

			if (set)
				delete set;
		}
		else
		{
			if (sql.Right(6) == wxT("bigint"))
				sql = wxT("bigserial");
			else if (sql.Right(8) == wxT("smallint"))
				sql = wxT("smallserial");
			else
				sql = wxT("serial");

			if (GetNotNull())
				sql += wxT(" NOT NULL");
		}
	}
	else
	{
		if (GetNotNull())
			sql += wxT(" NOT NULL");
		if (!GetGenerated().IsEmpty()) {
			if (GetGenerated()=="s") sql+= " GENERATED ALWAYS AS " + GetDefault() + " STORED";
			if (GetGenerated()=="v") sql+= " GENERATED ALWAYS AS " + GetDefault() + " VIRTUAL";
		} else if (!GetIdentity().IsEmpty()) {
			if (GetIdentity()=="a") sql+= " GENERATED ALWAYS AS IDENTITY";
			if (GetIdentity()=="d") sql+= " GENERATED BY DEFAULT AS IDENTITY" ;
		}
		else
			AppendIfFilled(sql, wxT(" DEFAULT "), GetDefault());
	}
	return sql;
}


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

		wxStringTokenizer indkey(pkCols);
		while (indkey.HasMoreTokens())
		{
			wxString str = indkey.GetNextToken();
			if (StrToLong(str) == GetColNumber())
			{
				isPK = true;
				break;
			}
		}

		if (!GetDatabase()->BackendMinimumVersion(7, 4))
		{
			// 7.3 misses the ANY(array) comparison
			pgSet *set = ExecuteSet(
			                 wxT("SELECT conkey\n")
			                 wxT("  FROM pg_constraint ct\n")
			                 wxT("  JOIN pg_class cl on cl.oid=confrelid\n")
			                 wxT(" WHERE contype='f' AND conrelid = ") + GetTableOidStr() + wxT("\n")
			                 wxT(" ORDER BY conname"));
			if (set)
			{
				wxString str;
				while (!isFK && !set->Eof())
				{
					wxStringTokenizer conkey(set->GetVal(0));

					while (conkey.HasMoreTokens())
					{
						str = conkey.GetNextToken();
						if (StrToLong(str.Mid(1)) == GetColNumber())
						{
							isFK = true;
							break;
						}
					}

					set->MoveNext();
				}
				delete set;
			}
		}
	}

	if (properties)
	{
		CreateListColumns(properties);

		properties->AppendItem(_("Name"), GetName());
		properties->AppendItem(_("Position"), GetColNumber());
		properties->AppendItem(_("Data type"), GetVarTypename());
		if (GetDatabase()->BackendMinimumVersion(9, 1))
		{
			properties->AppendItem(_("Collation"), GetCollation());
		}
		if (GetTable()->GetMetaType() != PGM_CATALOGOBJECT)
		{
			properties->AppendItem(_("Default"), GetDefault());
			if (GetTable()->GetMetaType() != PGM_VIEW && GetTable()->GetMetaType() != GP_EXTTABLE)
			{
				properties->AppendItem(_("Sequence"), database->GetSchemaPrefix(GetSerialSchema()) + GetSerialSequence());

				properties->AppendYesNoItem(_("Not NULL?"), GetNotNull());
				properties->AppendYesNoItem(_("Primary key?"), GetIsPK());
				properties->AppendYesNoItem(_("Foreign key?"), GetIsFK());
				properties->AppendItem(_("Storage"), GetStorage());
				if (GetInheritedCount() != 0)
				{
					properties->AppendItem(_("Inherited"),
					                       wxT("Yes (from table ") + GetInheritedTableName() + wxT(")"));
				}
				else
				{
					properties->AppendYesNoItem(_("Inherited"), false);
				}
				properties->AppendItem(_("Statistics"), GetAttstattarget());
				size_t i;
				for (i = 0 ; i < variables.GetCount() ; i++)
				{
					wxString item = variables.Item(i);
					properties->AppendItem(item.BeforeFirst('='), item.AfterFirst('='));
				}
				properties->AppendYesNoItem(_("System column?"), GetSystemObject());
			}
		}
		if (GetDatabase()->BackendMinimumVersion(8, 4))
		{
			properties->AppendItem(_("ACL"), GetAcl());
		}

		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));
				}
			}
		}
	}
}


void pgColumn::ShowStatistics(frmMain *form, ctlListView *statistics)
{
	DisplayStatistics(statistics,
	                  wxT("SELECT null_frac AS ") + qtIdent(_("Null Fraction")) +
	                  wxT(", avg_width AS ") + qtIdent(_("Average Width")) +
	                  wxT(", n_distinct AS ") + qtIdent(_("Distinct Values")) +
	                  wxT(", most_common_vals AS ") + qtIdent(_("Most Common Values")) +
	                  wxT(", most_common_freqs AS ") + qtIdent(_("Most Common Frequencies")) +
	                  wxT(", histogram_bounds AS ") + qtIdent(_("Histogram Bounds")) +
	                  wxT(", correlation AS ") + qtIdent(_("Correlation")) + wxT("\n")
	                  wxT("  FROM pg_stats\n")
	                  wxT(" WHERE schemaname = ") + qtDbString(schema->GetName()) + wxT("\n")
	                  wxT("   AND tablename = ") + qtDbString(GetTableName()) + wxT("\n")
	                  wxT("   AND attname = ") + qtDbString(GetName()));
}


pgObject *pgColumn::Refresh(ctlTree *browser, const wxTreeItemId item)
{
	pgObject *column = 0;
	pgCollection *coll = browser->GetParentCollection(item);
	if (coll)
		column = columnFactory.CreateObjects(coll, 0, wxT("\n   AND att.attnum=") + NumToStr(GetColNumber()));

	return column;
}



pgObject *pgColumnFactory::CreateObjects(pgCollection *coll, ctlTree *browser, const wxString &restriction)
{
	pgTableObjCollection *collection = (pgTableObjCollection *)coll;
	pgColumn *column = 0;
	pgDatabase *database = collection->GetDatabase();
	inheritHashMap inhMap;

	// grab inherited tables with attibute names
	pgSet *inhtables = database->ExecuteSet(
	                       wxT("SELECT\n")
	                       wxT("    array_to_string(array_agg(inhrelname), ', ') inhrelname,\n")
	                       wxT("    attrname\n")
	                       wxT("FROM\n")
	                       wxT("    (SELECT\n")
	                       wxT("        inhparent::regclass AS inhrelname,\n")
	                       wxT("        a.attname AS attrname\n")
	                       wxT("    FROM\n")
	                       wxT("        pg_inherits i\n")
	                       wxT("        LEFT JOIN pg_attribute a ON\n")
	                       wxT("            (attrelid = inhparent AND attnum > 0)\n")
	                       wxT("    WHERE inhrelid = ") + collection->GetOidStr() + wxT("::oid\n")
	                       wxT("    ORDER BY inhseqno) a\n")
	                       wxT("GROUP BY attrname"));

	if (inhtables)
	{
		while (!inhtables->Eof())
		{
			wxString attrName = inhtables->GetVal(wxT("attrname"));
			wxString inhrelName = inhtables->GetVal(wxT("inhrelname"));
			inhMap[attrName] = inhrelName;
			inhtables->MoveNext();
		}

		delete inhtables;
	}

	wxString systemRestriction;
	if (!settings->GetShowSystemObjects())
		systemRestriction = wxT("\n   AND att.attnum > 0");

	wxString sql =
	    wxT("SELECT att.*, def.*, pg_catalog.pg_get_expr(def.adbin, def.adrelid) AS defval, CASE WHEN att.attndims > 0 THEN 1 ELSE 0 END AS isarray, format_type(ty.oid,NULL) AS typname, format_type(ty.oid,att.atttypmod) AS displaytypname, tn.nspname as typnspname, et.typname as elemtypname,\n")
	    wxT("  ty.typstorage AS defaultstorage, cl.relname, na.nspname, coalesce(att.attstattarget,-1) attstattarget2, description, cs.relname AS sername, ns.nspname AS serschema,\n")
	    wxT("  (SELECT count(1) FROM pg_type t2 WHERE t2.typname=ty.typname) > 1 AS isdup, indkey");

	if (database->BackendMinimumVersion(9, 1))
		sql += wxT(",\n  coll.collname, nspc.nspname as collnspname");
	if (database->BackendMinimumVersion(8, 5))
		sql += wxT(",\n  attoptions");
	if (database->BackendMinimumVersion(7, 4))
		sql +=
		    wxT(",\n")
		    wxT("  EXISTS(SELECT 1 FROM  pg_constraint WHERE conrelid=att.attrelid AND contype='f'")
		    wxT(" AND att.attnum=ANY(conkey)) As isfk");
	if (database->BackendMinimumVersion(9, 1))
	{
		sql += wxT(",\n(SELECT array_agg(label) FROM pg_seclabels sl1 WHERE sl1.objoid=att.attrelid AND sl1.objsubid=att.attnum) AS labels");
		sql += wxT(",\n(SELECT array_agg(provider) FROM pg_seclabels sl2 WHERE sl2.objoid=att.attrelid AND sl2.objsubid=att.attnum) AS providers");
	}

	sql += wxT("\n")
	       wxT("  FROM pg_attribute att\n")
	       wxT("  JOIN pg_type ty ON ty.oid=atttypid\n")
	       wxT("  JOIN pg_namespace tn ON tn.oid=ty.typnamespace\n")
	       wxT("  JOIN pg_class cl ON cl.oid=att.attrelid\n")
	       wxT("  JOIN pg_namespace na ON na.oid=cl.relnamespace\n")
	       wxT("  LEFT OUTER JOIN pg_type et ON et.oid=ty.typelem\n")
	       wxT("  LEFT OUTER JOIN pg_attrdef def ON adrelid=att.attrelid AND adnum=att.attnum\n")
	       wxT("  LEFT OUTER JOIN pg_description des ON (des.objoid=att.attrelid AND des.objsubid=att.attnum AND des.classoid='pg_class'::regclass)\n")
	       wxT("  LEFT OUTER JOIN (pg_depend JOIN pg_class cs ON classid='pg_class'::regclass AND objid=cs.oid AND cs.relkind='S') ON refobjid=att.attrelid AND refobjsubid=att.attnum\n")
	       wxT("  LEFT OUTER JOIN pg_namespace ns ON ns.oid=cs.relnamespace\n")
	       wxT("  LEFT OUTER JOIN pg_index pi ON pi.indrelid=att.attrelid AND indisprimary\n");
	if (database->BackendMinimumVersion(9, 1))
		sql += wxT("  LEFT OUTER JOIN pg_collation coll ON att.attcollation=coll.oid\n")
		       wxT("  LEFT OUTER JOIN pg_namespace nspc ON coll.collnamespace=nspc.oid\n");
	sql += wxT(" WHERE att.attrelid = ") + collection->GetOidStr()
	       + restriction + systemRestriction + wxT("\n")
	       wxT("   AND att.attisdropped IS FALSE\n")
	       wxT(" ORDER BY att.attnum");

	pgSet *columns = database->ExecuteSet(sql);
	if (columns)
	{
		while (!columns->Eof())
		{
			wxString attrName = columns->GetVal(wxT("attname"));
			column = new pgColumn(collection->GetTable(), attrName);

			column->iSetAttTypId(columns->GetOid(wxT("atttypid")));
			column->iSetColNumber(columns->GetLong(wxT("attnum")));
			column->iSetIsArray(columns->GetBool(wxT("isarray")));
			column->iSetComment(columns->GetVal(wxT("description")));
			column->iSetSerialSequence(columns->GetVal(wxT("sername")));
			column->iSetSerialSchema(columns->GetVal(wxT("serschema")));
			if (database->BackendMinimumVersion(10, 0))
			{
				column->iSetIdentity(columns->GetVal(wxT("attidentity")));
			};
			if (database->BackendMinimumVersion(12, 0))
			{
				column->iSetGenerated(columns->GetVal(wxT("attgenerated")));
			};
			if (database->BackendMinimumVersion(14, 0))
			{
				wxString compres = columns->GetVal(wxT("attcompression"));
				column->iSetCompression(compres);
			}



			column->iSetPkCols(columns->GetVal(wxT("indkey")));
			if (database->BackendMinimumVersion(7, 4))
				column->iSetIsFK(columns->GetBool(wxT("isfk")));

			if (columns->GetBool(wxT("atthasdef")))
				column->iSetDefault(columns->GetVal(wxT("defval")));

			wxString storage = columns->GetVal(wxT("attstorage"));
			column->iSetStorage(
			    storage == wxT("p") ? wxT("PLAIN") :
			    storage == wxT("e") ? wxT("EXTERNAL") :
			    storage == wxT("m") ? wxT("MAIN") :
			    storage == wxT("x") ? wxT("EXTENDED") : wxT("Unknown"));
			wxString defaultStorage = columns->GetVal(wxT("defaultstorage"));
			column->iSetDefaultStorage(
			    defaultStorage == wxT("p") ? wxT("PLAIN") :
			    defaultStorage == wxT("e") ? wxT("EXTERNAL") :
			    defaultStorage == wxT("m") ? wxT("MAIN") :
			    defaultStorage == wxT("x") ? wxT("EXTENDED") : wxT("Unknown"));

			column->iSetTyplen(columns->GetLong(wxT("attlen")));

			long typmod = columns->GetLong(wxT("atttypmod"));
			pgDatatype dt(columns->GetVal(wxT("typnspname")), columns->GetVal(wxT("typname")),
			              columns->GetBool(wxT("isdup")),
			              columns->GetLong(wxT("attndims")), typmod);


			column->iSetTypmod(typmod);
			column->iSetLength(dt.Length());
			column->iSetPrecision(dt.Precision());
			column->iSetRawTypename(dt.Name());

			column->iSetVarTypename(dt.FullName());
			column->iSetQuotedTypename(columns->GetVal(wxT("displaytypname")));

			column->iSetNotNull(columns->GetBool(wxT("attnotnull")));
			column->iSetQuotedFullTable(database->GetQuotedSchemaPrefix(columns->GetVal(wxT("nspname")))
			                            + qtIdent(columns->GetVal(wxT("relname"))));
			column->iSetTableName(columns->GetVal(wxT("relname")));
			column->iSetInheritedCount(columns->GetLong(wxT("attinhcount")));

			// Check whether the attribute is inherited
			inheritHashMap::iterator it = inhMap.find(attrName);
			if (it != inhMap.end())
				column->iSetInheritedTableName(it->second);

			column->iSetIsLocal(columns->GetBool(wxT("attislocal")));
			column->iSetAttstattarget(columns->GetLong(wxT("attstattarget2")));
			if (database->BackendMinimumVersion(8, 5))
			{
				wxString str = columns->GetVal(wxT("attoptions"));
				if (!str.IsEmpty())
					FillArray(column->GetVariables(), str.Mid(1, str.Length() - 2));
			}
			if (database->BackendMinimumVersion(8, 4))
				column->iSetAcl(columns->GetVal(wxT("attacl")));
			if (database->BackendMinimumVersion(9, 1))
			{
				wxString coll = wxEmptyString;
				if (!columns->GetVal(wxT("collname")).IsEmpty())
					coll = qtIdent(columns->GetVal(wxT("collnspname"))) + wxT(".") + qtIdent(columns->GetVal(wxT("collname")));
				column->iSetCollation(coll);
			}

			if (database->BackendMinimumVersion(9, 1))
			{
				column->iSetProviders(columns->GetVal(wxT("providers")));
				column->iSetLabels(columns->GetVal(wxT("labels")));
			}

			if (browser)
			{
				browser->AppendObject(collection, column);
				columns->MoveNext();
			}
			else
				break;
		}

		delete columns;
	}

	inhMap.clear();
	return column;
}

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

pgColumnCollection::pgColumnCollection(pgaFactory *factory, pgTable *tbl)
	: pgTableObjCollection(factory, tbl)
{
}


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

	switch (kindOfMessage)
	{
		case RETRIEVINGDETAILS:
			message = _("Retrieving details on columns");
			break;
		case REFRESHINGDETAILS:
			message = _("Refreshing columns");
			break;
		case OBJECTSLISTREPORT:
			message = _("Columns list report");
			break;
	}

	return message;
}

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

#include "images/column.pngc"
#include "images/column-sm.pngc"
#include "images/columns.pngc"

pgColumnFactory::pgColumnFactory()
	: pgTableObjFactory(__("Column"), __("New Column..."), __("Create a new Column."), column_png_img, column_sm_png_img)
{
	metaType = PGM_COLUMN;
}


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

pgColumnFactory columnFactory;
static pgaCollectionFactory cf(&columnFactory, __("Columns"), columns_png_img);
