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(); odb->Init();
return odb; return odb;
} }
catch (const Pgsql::PgException &ex) { catch (const std::exception &ex) {
throw OpenDatabaseException(ex.what()); throw OpenDatabaseException(ex.what());
} }
} }

View file

@ -23,7 +23,7 @@ SELECT attrelid, attname, atttypid, attstattarget,
LEFT JOIN pg_attrdef AS def ON attrelid=adrelid AND attnum=adnum LEFT JOIN pg_attrdef AS def ON attrelid=adrelid AND attnum=adnum
LEFT 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 LEFT 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
LEFT JOIN pg_namespace ns ON ns.oid=cs.relnamespace LEFT JOIN pg_namespace ns ON ns.oid=cs.relnamespace
LEFT JOIN pg_catalog.pg_description AS d ON (objoid=attrelid AND d.objsubid=attnum))__"; LEFT JOIN pg_catalog.pg_description AS d ON (objoid=attrelid AND d.objsubid=attnum) )__";
return q; return q;
} }

View file

@ -1,4 +1,5 @@
#include "PgClass.h" #include "PgClass.h"
#include "PgAttribute.h"
#include "PgAttributeContainer.h" #include "PgAttributeContainer.h"
#include "PgClassContainer.h" #include "PgClassContainer.h"
#include "PgDatabaseCatalog.h" #include "PgDatabaseCatalog.h"
@ -7,6 +8,7 @@
#include <QStringBuilder> #include <QStringBuilder>
#include "SqlFormattingUtils.h" #include "SqlFormattingUtils.h"
#include <ranges> #include <ranges>
#include <cassert>
void operator<<(RelPersistence &s, const Pgsql::Value &v) 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 QString PgClass::createSql() const
{ {
if (createSqlCache.isEmpty()) if (createSqlCache.isEmpty())
@ -75,6 +96,7 @@ QString PgClass::createSql() const
switch (kind) switch (kind)
{ {
case RelKind::Table: case RelKind::Table:
case RelKind::PartitionedTable:
createSqlCache = createTableSql(); createSqlCache = createTableSql();
break; break;
case RelKind::View: case RelKind::View:
@ -118,7 +140,7 @@ QString PgClass::ddlTypeName() const
{ {
switch (kind) switch (kind)
{ {
case RelKind::PartitionedTable: return "PARTITIONED TABLE"; case RelKind::PartitionedTable:
return "TABLE"; return "TABLE";
default: default:
return PgNamespaceObject::ddlTypeName(); return PgNamespaceObject::ddlTypeName();
@ -145,7 +167,9 @@ QString PgClass::createTableSql() const
else else
sql += generateBodySql(false); sql += generateBodySql(false);
sql += generateInheritsSql() sql += generateInheritsSql()
% partitionBySql()
% generateTablespaceSql() % generateTablespaceSql()
% ";\n"; % ";\n";
return sql; return sql;
@ -238,6 +262,49 @@ QString PgClass::generateInheritsSql() const
return sql; 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 QString PgClass::getPartitionOfName() const
{ {
auto parents = catalog().inherits()->getParentsOf(oid()); auto parents = catalog().inherits()->getParentsOf(oid());
@ -274,4 +341,15 @@ QString PgClass::createViewSql() const
return sql; 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 "PgNamespaceObject.h"
#include <QString> #include <QString>
#include <libpq-fe.h> #include <libpq-fe.h>
#include <boost/container/small_vector.hpp>
class PgAttribute;
enum class RelPersistence { enum class RelPersistence {
Permanent, // p Permanent, // p
@ -30,6 +33,37 @@ enum class RelKind
void operator<<(RelKind &s, const Pgsql::Value &v); 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 { class PgClass: public PgNamespaceObject {
public: public:
@ -51,6 +85,7 @@ public:
std::vector<QString> options; std::vector<QString> options;
QString viewdef; QString viewdef;
QString partitionBoundaries; QString partitionBoundaries;
PgPartitionedTable partitionedTable; // ignore if RelKind != PartitionedTable
using PgNamespaceObject::PgNamespaceObject; using PgNamespaceObject::PgNamespaceObject;
@ -59,6 +94,7 @@ public:
QString typeName() const override; QString typeName() const override;
QString aclAllPattern() const override; QString aclAllPattern() const override;
protected: protected:
virtual QString ddlTypeName() const override; virtual QString ddlTypeName() const override;
@ -68,6 +104,11 @@ private:
QString createTableSql() const; QString createTableSql() const;
QString generateBodySql(bool isPartition) const; QString generateBodySql(bool isPartition) const;
QString generateInheritsSql() const; QString generateInheritsSql() const;
QString partitionBySql() const;
QString partitionKeySql() const;
QString partitionKeyItemSql(
const PartitioningKeyItem &keyItem
) const;
QString getPartitionOfName() const; QString getPartitionOfName() const;
QString generateTablespaceSql() const; QString generateTablespaceSql() const;
QString createViewSql() const; QString createViewSql() const;

View file

@ -17,16 +17,52 @@ std::string PgClassContainer::getLoadQuery() const
if (lessThenVersion(120000)) if (lessThenVersion(120000))
q += ", relhasoids "; q += ", relhasoids ";
if (minimumVersion(100000)) 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 += q +=
"\nFROM pg_catalog.pg_class \n" "\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')"; "WHERE relkind IN ('r', 'i', 'p', 'I', 'v', 'm', 'f')";
return q; 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) PgClass PgClassContainer::loadElem(const Pgsql::Row &row)
{ {
Pgsql::Col col(row); Pgsql::Col col(row);
@ -35,7 +71,7 @@ PgClass PgClassContainer::loadElem(const Pgsql::Row &row)
Oid schema_oid = col.nextValue(); Oid schema_oid = col.nextValue();
PgClass v(m_catalog, class_oid, name, schema_oid); PgClass v(m_catalog, class_oid, name, schema_oid);
Oid owner ; Oid owner;
col >> v.type >> v.oftype col >> v.type >> v.oftype
>> owner >> v.am >> v.filenode >> v.tablespace >> v.pages_est >> owner >> v.am >> v.filenode >> v.tablespace >> v.pages_est
>> v.tuples_est >> v.toastrelid >> v.isshared >> v.persistence >> v.tuples_est >> v.toastrelid >> v.isshared >> v.persistence
@ -55,8 +91,32 @@ PgClass PgClassContainer::loadElem(const Pgsql::Row &row)
if (lessThenVersion(120000)) if (lessThenVersion(120000))
col >> v.hasoids; col >> v.hasoids;
PgPartitionedTable &pt = v.partitionedTable;
if (minimumVersion(100000)) if (minimumVersion(100000))
{
PartitionedTableKeyItemsBuilder kibuilder;
col >> v.partitionBoundaries; 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; return v;
} }

View file

@ -4,6 +4,7 @@
#include "Pgsql_declare.h" #include "Pgsql_declare.h"
#include "Pgsql_Result.h" #include "Pgsql_Result.h"
#include <QString> #include <QString>
#include <functional>
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <libpq-fe.h> #include <libpq-fe.h>
@ -68,6 +69,15 @@ public:
return nullptr; 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 const T* getByName(const QString &name) const
{ {
auto find_res = std::find(m_container.begin(), m_container.end(), name); auto find_res = std::find(m_container.begin(), m_container.end(), name);

View file

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