2017-08-23 13:27:23 +02:00
|
|
|
|
#include "PgClass.h"
|
2023-02-06 20:03:57 +01:00
|
|
|
|
#include "PgAttribute.h"
|
2018-11-30 18:41:38 +01:00
|
|
|
|
#include "PgAttributeContainer.h"
|
2018-12-03 21:03:49 +01:00
|
|
|
|
#include "PgClassContainer.h"
|
2018-11-30 18:41:38 +01:00
|
|
|
|
#include "PgDatabaseCatalog.h"
|
|
|
|
|
|
#include "PgConstraintContainer.h"
|
2018-12-03 21:03:49 +01:00
|
|
|
|
#include "PgInheritsContainer.h"
|
2018-11-30 18:41:38 +01:00
|
|
|
|
#include <QStringBuilder>
|
|
|
|
|
|
#include "SqlFormattingUtils.h"
|
2023-01-18 19:43:12 +01:00
|
|
|
|
#include <ranges>
|
2023-02-06 20:03:57 +01:00
|
|
|
|
#include <cassert>
|
2017-02-01 18:01:02 +01:00
|
|
|
|
|
2018-11-25 19:45:06 +01:00
|
|
|
|
|
2017-12-10 10:35:46 +01:00
|
|
|
|
void operator<<(RelPersistence &s, const Pgsql::Value &v)
|
|
|
|
|
|
{
|
|
|
|
|
|
//s = static_cast<T>(v);
|
|
|
|
|
|
const char *c = v.c_str();
|
2022-01-20 20:13:56 +01:00
|
|
|
|
switch (*c)
|
|
|
|
|
|
{
|
2017-12-10 10:35:46 +01:00
|
|
|
|
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();
|
2022-01-20 20:13:56 +01:00
|
|
|
|
switch (*c)
|
|
|
|
|
|
{
|
2023-02-06 20:03:57 +01:00
|
|
|
|
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");
|
2017-12-10 10:35:46 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2018-11-25 19:45:06 +01:00
|
|
|
|
|
2023-02-06 20:03:57 +01:00
|
|
|
|
void operator<<(PartitioningStrategy &s, const Pgsql::Value &v)
|
|
|
|
|
|
{
|
|
|
|
|
|
const char *c = v.c_str();
|
|
|
|
|
|
switch (*c)
|
|
|
|
|
|
{
|
|
|
|
|
|
case 'h':
|
|
|
|
|
|
s = PartitioningStrategy::Hash;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'l':
|
|
|
|
|
|
s = PartitioningStrategy::List;
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'r':
|
|
|
|
|
|
s = PartitioningStrategy::Range;
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw std::runtime_error("Unknown PartitioningStrategy");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-11-30 18:41:38 +01:00
|
|
|
|
QString PgClass::createSql() const
|
|
|
|
|
|
{
|
2022-01-20 20:13:56 +01:00
|
|
|
|
if (createSqlCache.isEmpty())
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (kind)
|
|
|
|
|
|
{
|
|
|
|
|
|
case RelKind::Table:
|
2023-02-06 20:03:57 +01:00
|
|
|
|
case RelKind::PartitionedTable:
|
2022-01-20 20:13:56 +01:00
|
|
|
|
createSqlCache = createTableSql();
|
|
|
|
|
|
break;
|
|
|
|
|
|
case RelKind::View:
|
|
|
|
|
|
createSqlCache = createViewSql();
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2018-11-30 18:41:38 +01:00
|
|
|
|
}
|
|
|
|
|
|
return createSqlCache;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-12-24 11:31:56 +01:00
|
|
|
|
QString PgClass::typeName() const
|
|
|
|
|
|
{
|
2022-01-20 20:13:56 +01:00
|
|
|
|
switch (kind)
|
|
|
|
|
|
{
|
2023-01-07 07:44:33 +01:00
|
|
|
|
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";
|
2018-12-24 11:31:56 +01:00
|
|
|
|
}
|
|
|
|
|
|
throw std::runtime_error("Unexpected value in PgClass::typeName()");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-12-27 12:07:19 +01:00
|
|
|
|
QString PgClass::aclAllPattern() const
|
|
|
|
|
|
{
|
2022-01-20 20:13:56 +01:00
|
|
|
|
switch (kind)
|
|
|
|
|
|
{
|
2018-12-27 12:07:19 +01:00
|
|
|
|
case RelKind::Table: return "arwdDxt";
|
|
|
|
|
|
default:
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2023-01-30 20:08:24 +01:00
|
|
|
|
return {};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QString PgClass::ddlTypeName() const
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (kind)
|
|
|
|
|
|
{
|
2023-02-06 20:03:57 +01:00
|
|
|
|
case RelKind::PartitionedTable:
|
2023-01-30 20:08:24 +01:00
|
|
|
|
return "TABLE";
|
|
|
|
|
|
default:
|
|
|
|
|
|
return PgNamespaceObject::ddlTypeName();
|
|
|
|
|
|
}
|
2018-12-27 12:07:19 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-11-30 18:41:38 +01:00
|
|
|
|
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();
|
|
|
|
|
|
|
2023-01-18 19:43:12 +01:00
|
|
|
|
if (!partitionBoundaries.isEmpty())
|
|
|
|
|
|
{
|
|
|
|
|
|
sql += " PARTITION OF " + getPartitionOfName();
|
|
|
|
|
|
sql += generateBodySql(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
sql += generateBodySql(false);
|
|
|
|
|
|
|
2023-02-06 20:03:57 +01:00
|
|
|
|
|
2023-01-18 19:43:12 +01:00
|
|
|
|
sql += generateInheritsSql()
|
2023-02-06 20:03:57 +01:00
|
|
|
|
% partitionBySql()
|
2023-01-18 19:43:12 +01:00
|
|
|
|
% 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;
|
2022-01-20 20:13:56 +01:00
|
|
|
|
for (auto && col : cols)
|
|
|
|
|
|
{
|
2023-01-18 19:43:12 +01:00
|
|
|
|
if (first)
|
2022-01-20 20:13:56 +01:00
|
|
|
|
{
|
2023-01-18 19:43:12 +01:00
|
|
|
|
first = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
sql += ",\n ";
|
|
|
|
|
|
if (!col.islocal) sql += "-- ";
|
|
|
|
|
|
sql += col.columnDefinition(catalog());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2022-01-20 20:13:56 +01:00
|
|
|
|
for (auto && constraint: constraints)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (first)
|
|
|
|
|
|
{
|
2023-01-18 19:43:12 +01:00
|
|
|
|
sql += "\n ";
|
|
|
|
|
|
first = false;
|
|
|
|
|
|
}
|
2022-01-21 18:53:47 +01:00
|
|
|
|
else
|
|
|
|
|
|
sql += ",\n ";
|
2023-01-18 19:43:12 +01:00
|
|
|
|
sql += getConstraintDefinition(catalog(), constraint, " ");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sql += "\n)";
|
2018-11-30 18:41:38 +01:00
|
|
|
|
|
2022-01-21 18:53:47 +01:00
|
|
|
|
return sql;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QString PgClass::generateInheritsSql() const
|
|
|
|
|
|
{
|
2023-01-18 19:43:12 +01:00
|
|
|
|
if (!partitionBoundaries.isEmpty())
|
|
|
|
|
|
return "\n" + partitionBoundaries;
|
|
|
|
|
|
|
2022-01-21 18:53:47 +01:00
|
|
|
|
QString sql;
|
|
|
|
|
|
// [ INHERITS ( parent_table [, ... ] ) ]
|
|
|
|
|
|
auto parents = catalog().inherits()->getParentsOf(oid());
|
|
|
|
|
|
if (!parents.empty())
|
|
|
|
|
|
{
|
|
|
|
|
|
sql += "\nINHERITS (";
|
|
|
|
|
|
bool first = true;
|
|
|
|
|
|
for (auto parent_oid : parents)
|
2022-01-20 20:13:56 +01:00
|
|
|
|
{
|
2022-01-21 18:53:47 +01:00
|
|
|
|
if (first) first = false;
|
|
|
|
|
|
else sql += ", ";
|
|
|
|
|
|
sql += catalog().classes()->getByKey(parent_oid)->fullyQualifiedQuotedObjectName();
|
|
|
|
|
|
}
|
|
|
|
|
|
sql += ")";
|
|
|
|
|
|
}
|
|
|
|
|
|
return sql;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-02-06 20:03:57 +01:00
|
|
|
|
QString PgClass::partitionBySql() const
|
|
|
|
|
|
{
|
|
|
|
|
|
if (kind != RelKind::PartitionedTable)
|
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
|
|
QString sql = "\nPARTITION BY " % PartitionStrategyKeyword(partitionedTable.strategy);
|
|
|
|
|
|
sql += partitionKeySql();
|
|
|
|
|
|
return sql;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QString PgClass::partitionKeySql() const
|
|
|
|
|
|
{
|
|
|
|
|
|
QString result;
|
|
|
|
|
|
result += "(";
|
|
|
|
|
|
auto keyItem = partitionedTable.keyColumns.begin();
|
|
|
|
|
|
if (keyItem != partitionedTable.keyColumns.end())
|
|
|
|
|
|
{
|
|
|
|
|
|
result += partitionKeyItemSql(*keyItem);
|
|
|
|
|
|
for (++keyItem; keyItem != partitionedTable.keyColumns.end(); ++keyItem)
|
|
|
|
|
|
result += ", " % partitionKeyItemSql(*keyItem);
|
|
|
|
|
|
}
|
|
|
|
|
|
result += ")";
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QString PgClass::partitionKeyItemSql(
|
|
|
|
|
|
const PartitioningKeyItem &keyItem
|
|
|
|
|
|
) const
|
|
|
|
|
|
{
|
|
|
|
|
|
if (keyItem.attNum == 0)
|
2023-03-21 16:20:25 +01:00
|
|
|
|
return keyItem.expression;
|
2023-02-06 20:03:57 +01:00
|
|
|
|
|
|
|
|
|
|
const PgAttribute *col = catalog().attributes()->findIf(
|
|
|
|
|
|
[this, &keyItem] (const auto &att)
|
|
|
|
|
|
{
|
|
|
|
|
|
return att.relid == oid() && att.num == keyItem.attNum;
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
assert(col != nullptr);
|
|
|
|
|
|
return quoteIdent(col->name);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-01-18 19:43:12 +01:00
|
|
|
|
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");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-01-21 18:53:47 +01:00
|
|
|
|
QString PgClass::generateTablespaceSql() const
|
|
|
|
|
|
{
|
2022-01-20 20:13:56 +01:00
|
|
|
|
if (tablespace != 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
auto ns = getTablespaceDisplayString(catalog(), tablespace);
|
2022-01-21 18:53:47 +01:00
|
|
|
|
return "\n TABLESPACE " % quoteIdent(ns);
|
2022-01-20 20:13:56 +01:00
|
|
|
|
}
|
2022-01-21 18:53:47 +01:00
|
|
|
|
return {};
|
2018-11-30 18:41:38 +01:00
|
|
|
|
}
|
2019-10-26 13:13:58 +02:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-02-06 20:03:57 +01:00
|
|
|
|
QString PartitionStrategyKeyword(PartitioningStrategy ps)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (ps) {
|
|
|
|
|
|
case PartitioningStrategy::Hash:
|
|
|
|
|
|
return "HASH";
|
|
|
|
|
|
case PartitioningStrategy::List:
|
|
|
|
|
|
return "LIST";
|
|
|
|
|
|
case PartitioningStrategy::Range:
|
|
|
|
|
|
return "RANGE";
|
|
|
|
|
|
}
|
|
|
|
|
|
throw std::runtime_error("Unknown PartitioningStrategy");
|
|
|
|
|
|
}
|