Generate PARTITIONED BY SQL for partitioned tables.
Expressions not yet supported.
This commit is contained in:
parent
61f90668d8
commit
2c899bd799
8 changed files with 243 additions and 41 deletions
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -33,41 +35,60 @@ void operator<<(RelKind &s, const Pgsql::Value &v)
|
||||||
const char *c = v.c_str();
|
const char *c = v.c_str();
|
||||||
switch (*c)
|
switch (*c)
|
||||||
{
|
{
|
||||||
case 'r':
|
case 'r':
|
||||||
s = RelKind::Table;
|
s = RelKind::Table;
|
||||||
break;
|
break;
|
||||||
case 'i':
|
case 'i':
|
||||||
s = RelKind::Index;
|
s = RelKind::Index;
|
||||||
break;
|
break;
|
||||||
case 'S':
|
case 'S':
|
||||||
s = RelKind::Sequence;
|
s = RelKind::Sequence;
|
||||||
break;
|
break;
|
||||||
case 'v':
|
case 'v':
|
||||||
s = RelKind::View;
|
s = RelKind::View;
|
||||||
break;
|
break;
|
||||||
case 'm':
|
case 'm':
|
||||||
s = RelKind::MaterializedView;
|
s = RelKind::MaterializedView;
|
||||||
break;
|
break;
|
||||||
case 'c':
|
case 'c':
|
||||||
s = RelKind::Composite;
|
s = RelKind::Composite;
|
||||||
break;
|
break;
|
||||||
case 't':
|
case 't':
|
||||||
s = RelKind::Toast;
|
s = RelKind::Toast;
|
||||||
break;
|
break;
|
||||||
case 'f':
|
case 'f':
|
||||||
s = RelKind::ForeignTable;
|
s = RelKind::ForeignTable;
|
||||||
break;
|
break;
|
||||||
case 'p':
|
case 'p':
|
||||||
s = RelKind::PartitionedTable;
|
s = RelKind::PartitionedTable;
|
||||||
break;
|
break;
|
||||||
case 'I':
|
case 'I':
|
||||||
s = RelKind::PartitionedIndex;
|
s = RelKind::PartitionedIndex;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw std::runtime_error("Unknown RelKind");
|
throw std::runtime_error("Unknown RelKind");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -14,19 +14,55 @@ std::string PgClassContainer::getLoadQuery() const
|
||||||
" reloptions, d.description, "
|
" reloptions, d.description, "
|
||||||
" relacl, pg_get_viewdef(pg_class.oid)";
|
" relacl, pg_get_viewdef(pg_class.oid)";
|
||||||
|
|
||||||
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";
|
||||||
|
|
||||||
q +=
|
// pg_get_expr must be called on each element in partexprs
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
8
releasenotes/notes/partition-by-414aefaf17009287.yaml
Normal file
8
releasenotes/notes/partition-by-414aefaf17009287.yaml
Normal 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.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue