pgLab/pglablib/catalog/PgClass.cpp
eelke 60fb4ce285 Improve support for declarative partitioning.
Generated SQL for a partition is now correct (atleast for simple cases)
Switched to C++ 20 so the ranges library can be used in this case
to filter unwanted items.
2023-01-18 19:43:12 +01:00

266 lines
6.3 KiB
C++

#include "PgClass.h"
#include "PgAttributeContainer.h"
#include "PgClassContainer.h"
#include "PgDatabaseCatalog.h"
#include "PgConstraintContainer.h"
#include "PgInheritsContainer.h"
#include <QStringBuilder>
#include "SqlFormattingUtils.h"
#include <ranges>
void operator<<(RelPersistence &s, const Pgsql::Value &v)
{
//s = static_cast<T>(v);
const char *c = v.c_str();
switch (*c)
{
case 'p':
s = RelPersistence::Permanent;
break;
case 'u':
s = RelPersistence::Unlogged;
break;
case 't':
s = RelPersistence::Temporary;
break;
}
}
void operator<<(RelKind &s, const Pgsql::Value &v)
{
//s = static_cast<T>(v);
const char *c = v.c_str();
switch (*c)
{
case 'r':
s = RelKind::Table;
break;
case 'i':
s = RelKind::Index;
break;
case 'S':
s = RelKind::Sequence;
break;
case 'v':
s = RelKind::View;
break;
case 'm':
s = RelKind::MaterializedView;
break;
case 'c':
s = RelKind::Composite;
break;
case 't':
s = RelKind::Toast;
break;
case 'f':
s = RelKind::ForeignTable;
break;
case 'p':
s = RelKind::PartitionedTable;
break;
case 'I':
s = RelKind::PartitionedIndex;
break;
default:
throw std::runtime_error("Unknown RelKind");
}
}
QString PgClass::createSql() const
{
if (createSqlCache.isEmpty())
{
switch (kind)
{
case RelKind::Table:
createSqlCache = createTableSql();
break;
case RelKind::View:
createSqlCache = createViewSql();
break;
}
}
return createSqlCache;
}
QString PgClass::typeName() const
{
switch (kind)
{
case RelKind::Table: return "TABLE";
case RelKind::Index: return "INDEX";
case RelKind::Sequence: return "SEQUENCE";
case RelKind::View: return "VIEW";
case RelKind::MaterializedView: return "MATERIALIZED VIEW";
case RelKind::Composite: return "COMPOSITE";
case RelKind::Toast: return "TOAST";
case RelKind::ForeignTable: return "FOREIGN TABLE";
case RelKind::PartitionedTable: return "PARTITIONED TABLE";
case RelKind::PartitionedIndex: return "PARTITIONED INDEX";
}
throw std::runtime_error("Unexpected value in PgClass::typeName()");
}
QString PgClass::aclAllPattern() const
{
switch (kind)
{
case RelKind::Table: return "arwdDxt";
default:
break;
}
return {};
}
QString PgClass::createTableSql() const
{
QString sql;
// CREATE [ TEMP | UNLOGGED ] TABLE [ IF NOT EXISTS ] table_name ( [
sql += "CREATE ";
if (persistence == RelPersistence::Unlogged)
sql += "UNLOGGED ";
else if (persistence == RelPersistence::Temporary)
sql += "TEMP ";
sql += "TABLE ";
sql += fullyQualifiedQuotedObjectName();
if (!partitionBoundaries.isEmpty())
{
sql += " PARTITION OF " + getPartitionOfName();
sql += generateBodySql(true);
}
else
sql += generateBodySql(false);
sql += generateInheritsSql()
% generateTablespaceSql()
% ";\n";
return sql;
}
QString PgClass::generateBodySql(bool isPartition) const
{
// - also remove commented inherited column list? They are listed in the column view no need for them in sql
// - mark them in the view as inherited?
// - detect when body empty and leave it out completely (only for partitions, is leaving it out always legal?)
// - need to detect "inherited" constraint because these should not be listed either
auto colsFilter = isPartition ?
[] (const PgAttribute &c)
{
return c.num > 0 // ignore system columns
&& !c.isdropped // ignore dropped columns
&& c.islocal;
}
:
[] (const PgAttribute &c)
{
return c.num > 0 // ignore system columns
&& !c.isdropped; // ignore dropped columns
}
;
auto && cols = catalog().attributes()->getColumnsForRelation(oid())
| std::views::filter(colsFilter);
auto && constraints = catalog().constraints()->getConstraintsForRelation(oid())
| std::views::filter([] (const auto &c) { return c.islocal; });
if (cols.empty() && constraints.empty())
return {};
QString sql = " (\n ";
bool first = true;
for (auto && col : cols)
{
if (first)
{
first = false;
}
else
sql += ",\n ";
if (!col.islocal) sql += "-- ";
sql += col.columnDefinition(catalog());
}
for (auto && constraint: constraints)
{
if (first)
{
sql += "\n ";
first = false;
}
else
sql += ",\n ";
sql += getConstraintDefinition(catalog(), constraint, " ");
}
sql += "\n)";
return sql;
}
QString PgClass::generateInheritsSql() const
{
if (!partitionBoundaries.isEmpty())
return "\n" + partitionBoundaries;
QString sql;
// [ INHERITS ( parent_table [, ... ] ) ]
auto parents = catalog().inherits()->getParentsOf(oid());
if (!parents.empty())
{
sql += "\nINHERITS (";
bool first = true;
for (auto parent_oid : parents)
{
if (first) first = false;
else sql += ", ";
sql += catalog().classes()->getByKey(parent_oid)->fullyQualifiedQuotedObjectName();
}
sql += ")";
}
return sql;
}
QString PgClass::getPartitionOfName() const
{
auto parents = catalog().inherits()->getParentsOf(oid());
if (!parents.empty())
{
return catalog().classes()->getByKey(parents.front())->fullyQualifiedQuotedObjectName();
}
throw std::logic_error("Should only be called if there is a parent table");
}
QString PgClass::generateTablespaceSql() const
{
if (tablespace != 0)
{
auto ns = getTablespaceDisplayString(catalog(), tablespace);
return "\n TABLESPACE " % quoteIdent(ns);
}
return {};
}
QString PgClass::createViewSql() const
{
QString sql;
sql += "CREATE OR REPLACE VIEW " + fullyQualifiedQuotedObjectName();
// todo security_barrier
// todo check_option
sql += " AS \n";
sql += viewdef;
// todo owner
return sql;
}