Generate PARTITIONED BY SQL for partitioned tables.

Expressions not yet supported.
This commit is contained in:
eelke 2023-02-06 20:03:57 +01:00
parent 61f90668d8
commit 2c899bd799
8 changed files with 243 additions and 41 deletions

View file

@ -12,7 +12,7 @@ OpenDatabase::OpenDatabaseSPtr OpenDatabase::createOpenDatabase(const Connection
odb->Init();
return odb;
}
catch (const Pgsql::PgException &ex) {
catch (const std::exception &ex) {
throw OpenDatabaseException(ex.what());
}
}

View file

@ -1,4 +1,5 @@
#include "PgClass.h"
#include "PgAttribute.h"
#include "PgAttributeContainer.h"
#include "PgClassContainer.h"
#include "PgDatabaseCatalog.h"
@ -7,6 +8,7 @@
#include <QStringBuilder>
#include "SqlFormattingUtils.h"
#include <ranges>
#include <cassert>
void operator<<(RelPersistence &s, const Pgsql::Value &v)
@ -68,6 +70,25 @@ void operator<<(RelKind &s, const Pgsql::Value &v)
}
}
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");
}
}
QString PgClass::createSql() const
{
if (createSqlCache.isEmpty())
@ -75,6 +96,7 @@ QString PgClass::createSql() const
switch (kind)
{
case RelKind::Table:
case RelKind::PartitionedTable:
createSqlCache = createTableSql();
break;
case RelKind::View:
@ -118,7 +140,7 @@ QString PgClass::ddlTypeName() const
{
switch (kind)
{
case RelKind::PartitionedTable: return "PARTITIONED TABLE";
case RelKind::PartitionedTable:
return "TABLE";
default:
return PgNamespaceObject::ddlTypeName();
@ -145,7 +167,9 @@ QString PgClass::createTableSql() const
else
sql += generateBodySql(false);
sql += generateInheritsSql()
% partitionBySql()
% generateTablespaceSql()
% ";\n";
return sql;
@ -238,6 +262,49 @@ QString PgClass::generateInheritsSql() const
return sql;
}
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)
return "\"<expr>\""; // TODO add expression support for now use this to prevent a crash here because column 0 does not exist
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);
}
QString PgClass::getPartitionOfName() const
{
auto parents = catalog().inherits()->getParentsOf(oid());
@ -274,4 +341,15 @@ QString PgClass::createViewSql() const
return sql;
}
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");
}

View file

@ -5,6 +5,9 @@
#include "PgNamespaceObject.h"
#include <QString>
#include <libpq-fe.h>
#include <boost/container/small_vector.hpp>
class PgAttribute;
enum class RelPersistence {
Permanent, // p
@ -30,6 +33,37 @@ enum class RelKind
void operator<<(RelKind &s, const Pgsql::Value &v);
enum class PartitioningStrategy
{
Hash, // h
List, // l
Range // r
};
void operator<<(PartitioningStrategy &s, const Pgsql::Value &v);
QString PartitionStrategyKeyword(PartitioningStrategy ps);
class PartitioningKeyItem
{
public:
int16_t attNum;
Oid opClass;
Oid collation;
// expre nodetree
QString expression; // pg_get_expr(pg_node_tree, relation_oid)
};
using PartitioningKeyItems = boost::container::small_vector<PartitioningKeyItem, 3>;
class PgPartitionedTable
{
public:
PartitioningStrategy strategy;
Oid defaultPartition;
PartitioningKeyItems keyColumns;
};
class PgClass: public PgNamespaceObject {
public:
@ -51,6 +85,7 @@ public:
std::vector<QString> options;
QString viewdef;
QString partitionBoundaries;
PgPartitionedTable partitionedTable; // ignore if RelKind != PartitionedTable
using PgNamespaceObject::PgNamespaceObject;
@ -59,6 +94,7 @@ public:
QString typeName() const override;
QString aclAllPattern() const override;
protected:
virtual QString ddlTypeName() const override;
@ -68,6 +104,11 @@ private:
QString createTableSql() const;
QString generateBodySql(bool isPartition) const;
QString generateInheritsSql() const;
QString partitionBySql() const;
QString partitionKeySql() const;
QString partitionKeyItemSql(
const PartitioningKeyItem &keyItem
) const;
QString getPartitionOfName() const;
QString generateTablespaceSql() const;
QString createViewSql() const;

View file

@ -17,16 +17,52 @@ std::string PgClassContainer::getLoadQuery() const
if (lessThenVersion(120000))
q += ", relhasoids ";
if (minimumVersion(100000))
q += ", pg_get_expr(relpartbound, oid)"; // partition specification
q +=
", pg_get_expr(relpartbound, oid)"
", partstrat, partnatts, partattrs, partclass, partcollation"; // TODO: , partexprs";
if (minimumVersion(110000))
q += ", partdefid";
// pg_get_expr must be called on each element in partexprs
q +=
"\nFROM pg_catalog.pg_class \n"
" LEFT JOIN pg_catalog.pg_description AS d ON (objoid=pg_class.oid AND objsubid=0) \n"
" LEFT JOIN pg_catalog.pg_description AS d ON (objoid=pg_class.oid AND objsubid=0) \n";
if (minimumVersion(100000))
q += " LEFT JOIN pg_partitioned_table AS pt ON (partrelid=pg_class.oid) \n";
q +=
"WHERE relkind IN ('r', 'i', 'p', 'I', 'v', 'm', 'f')";
return q;
}
namespace {
class PartitionedTableKeyItemsBuilder {
public:
int16_t attCount;
std::vector<int16_t> attNums;
std::vector<Oid> attOpClass;
std::vector<Oid> attCollation;
PartitioningKeyItems Build()
{
PartitioningKeyItems result(attCount);
for (int attIdx = 0; attIdx < attCount; ++attIdx)
{
auto& item = result[attIdx];
item.attNum = attNums[attIdx];
item.opClass = attOpClass[attIdx];
item.collation = attCollation[attIdx];
}
return result;
}
};
}
PgClass PgClassContainer::loadElem(const Pgsql::Row &row)
{
Pgsql::Col col(row);
@ -55,8 +91,32 @@ PgClass PgClassContainer::loadElem(const Pgsql::Row &row)
if (lessThenVersion(120000))
col >> v.hasoids;
PgPartitionedTable &pt = v.partitionedTable;
if (minimumVersion(100000))
{
PartitionedTableKeyItemsBuilder kibuilder;
col >> v.partitionBoundaries;
auto strategy = col.nextValue();
if (strategy.null())
{
int s = minimumVersion(110000) ? 5 : 4;
col.skip(s);
}
else
{
pt.strategy << strategy;
col >> kibuilder.attCount;
col.getAsVector<int16_t>(std::back_inserter(kibuilder.attNums));
col.getAsVector<Oid>(std::back_inserter(kibuilder.attOpClass));
col.getAsVector<Oid>(std::back_inserter(kibuilder.attCollation));
pt.keyColumns = kibuilder.Build();
if (minimumVersion(110000))
col >> pt.defaultPartition;
}
}
return v;
}

View file

@ -4,6 +4,7 @@
#include "Pgsql_declare.h"
#include "Pgsql_Result.h"
#include <QString>
#include <functional>
#include <memory>
#include <vector>
#include <libpq-fe.h>
@ -68,6 +69,15 @@ public:
return nullptr;
}
const T* findIf(std::function<bool(const T&)> func) const
{
auto findResult = std::find_if(m_container.begin(), m_container.end(), func);
if (findResult != m_container.end())
return &*findResult;
return nullptr;
}
const T* getByName(const QString &name) const
{
auto find_res = std::find(m_container.begin(), m_container.end(), name);

View file

@ -19,6 +19,11 @@ namespace Pgsql {
return row.get(++col);
}
void skip(int n = 1)
{
col += n;
}
template <typename E, typename I>
Col& getAsArray(I insert_iter, NullHandling nullhandling = NullHandling::Throw)
{

View file

@ -0,0 +1,8 @@
---
features:
- |
Limited support generating SQL for partitioned tables. It should be able to
generate SQL for list, hash and range partitioning involving columns only.
issues:
- |
It can yet generate correct SQL for a table partitioned by an expression.