The list of indexes on a table now also shows the access method (ie btree)
This commit is contained in:
parent
7c4f1a4752
commit
50cb21b6f9
17 changed files with 198 additions and 26 deletions
|
|
@ -84,6 +84,31 @@ private:
|
||||||
using PKeyValues = std::vector<std::string>;
|
using PKeyValues = std::vector<std::string>;
|
||||||
|
|
||||||
|
|
||||||
|
class ColumnSort {
|
||||||
|
public:
|
||||||
|
enum Direction { Ascending, Descending };
|
||||||
|
enum NullSorting {
|
||||||
|
Default, ///< Behaves like NULL values are larger then non NULL values ASC NULLS LAST or DESC NULLS FIRST
|
||||||
|
First,
|
||||||
|
Last };
|
||||||
|
|
||||||
|
std::string columnName;
|
||||||
|
Direction direction = Direction::Ascending;
|
||||||
|
NullSorting nulls = NullSorting::Default;
|
||||||
|
|
||||||
|
std::string toSql() const
|
||||||
|
{
|
||||||
|
std::string res = columnName;
|
||||||
|
if (direction == Direction::Descending)
|
||||||
|
res += " DESC";
|
||||||
|
if (nulls == NullSorting::First)
|
||||||
|
res += " NULLS FIRST";
|
||||||
|
else if (nulls == NullSorting::Last)
|
||||||
|
res += " NULLS LAST";
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// using RowData = std::vector<std::string>;
|
// using RowData = std::vector<std::string>;
|
||||||
// using RowDataPtr = std::unique_ptr<RowData>;
|
// using RowDataPtr = std::unique_ptr<RowData>;
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ void IndexModel::setData(std::shared_ptr<const PgDatabaseCatalog> cat, const PgC
|
||||||
|
|
||||||
int IndexModel::rowCount(const QModelIndex &/*parent*/) const
|
int IndexModel::rowCount(const QModelIndex &/*parent*/) const
|
||||||
{
|
{
|
||||||
return m_indexes.size();
|
return (int)m_indexes.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
int IndexModel::columnCount(const QModelIndex &/*parent*/) const
|
int IndexModel::columnCount(const QModelIndex &/*parent*/) const
|
||||||
|
|
@ -43,6 +43,9 @@ QVariant IndexModel::headerData(int section, Qt::Orientation orientation, int ro
|
||||||
case NameCol:
|
case NameCol:
|
||||||
c = tr("Name");
|
c = tr("Name");
|
||||||
break;
|
break;
|
||||||
|
case AmCol:
|
||||||
|
c = tr("AM");
|
||||||
|
break;
|
||||||
case ColumnsCol:
|
case ColumnsCol:
|
||||||
c = tr("On");
|
c = tr("On");
|
||||||
break;
|
break;
|
||||||
|
|
@ -75,6 +78,10 @@ QVariant IndexModel::getData(const QModelIndex &index) const
|
||||||
v = getIndexDisplayString(*m_catalog, dat.indexrelid);
|
v = getIndexDisplayString(*m_catalog, dat.indexrelid);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case AmCol:
|
||||||
|
v = dat.getAm();
|
||||||
|
break;
|
||||||
|
|
||||||
case ColumnsCol:
|
case ColumnsCol:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,23 @@ public:
|
||||||
enum e_Columns : int {
|
enum e_Columns : int {
|
||||||
TypeCol, /// primary/unique/normal
|
TypeCol, /// primary/unique/normal
|
||||||
NameCol, ///
|
NameCol, ///
|
||||||
|
AmCol, ///< Access Method
|
||||||
ColumnsCol, ///
|
ColumnsCol, ///
|
||||||
ConditionCol,
|
ConditionCol,
|
||||||
colCount };
|
colCount };
|
||||||
|
|
||||||
|
// oid
|
||||||
|
// tablespace
|
||||||
|
// operator class
|
||||||
|
// unique
|
||||||
|
// primary
|
||||||
|
// clustered
|
||||||
|
// valid
|
||||||
|
// constraint
|
||||||
|
// system index
|
||||||
|
// fill factor
|
||||||
|
// comment
|
||||||
|
|
||||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||||
void setData(std::shared_ptr<const PgDatabaseCatalog> cat, const PgClass &table);
|
void setData(std::shared_ptr<const PgDatabaseCatalog> cat, const PgClass &table);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -542,18 +542,18 @@ void QueryTab::markError(const Pgsql::ErrorDetails &details)
|
||||||
if (details.state == "42703") {
|
if (details.state == "42703") {
|
||||||
std::size_t pos = details.messagePrimary.find('"');
|
std::size_t pos = details.messagePrimary.find('"');
|
||||||
if (pos != std::string::npos) {
|
if (pos != std::string::npos) {
|
||||||
int pos2 = details.messagePrimary.find('"', pos+1);
|
std::size_t pos2 = details.messagePrimary.find('"', pos+1);
|
||||||
if (pos2 != std::string::npos) {
|
if (pos2 != std::string::npos) {
|
||||||
length = pos2 - pos;
|
length = static_cast<int>(pos2 - pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (details.state == "42P01") {
|
else if (details.state == "42P01") {
|
||||||
std::size_t pos = details.messagePrimary.find('"');
|
std::size_t pos = details.messagePrimary.find('"');
|
||||||
if (pos != std::string::npos) {
|
if (pos != std::string::npos) {
|
||||||
int pos2 = details.messagePrimary.find('"', pos+1);
|
std::size_t pos2 = details.messagePrimary.find('"', pos+1);
|
||||||
if (pos2 != std::string::npos) {
|
if (pos2 != std::string::npos) {
|
||||||
length = pos2 - pos;
|
length = static_cast<int>(pos2 - pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -623,11 +623,11 @@ std::vector<QAction*> QueryTab::getToolbarActions()
|
||||||
action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_S));
|
action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_S));
|
||||||
connect(action, &QAction::triggered, this, &QueryTab::save);
|
connect(action, &QAction::triggered, this, &QueryTab::save);
|
||||||
actions.push_back(action);
|
actions.push_back(action);
|
||||||
// Save as
|
// Save as (menu only)
|
||||||
action = new QAction(QIcon(":/icons/script_go.png"), tr("Save SQL as"), this);
|
// action = new QAction(QIcon(":/icons/script_save.png"), tr("Save SQL as"), this);
|
||||||
//action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_S));
|
// //action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_S));
|
||||||
connect(action, &QAction::triggered, this, &QueryTab::saveAs);
|
// connect(action, &QAction::triggered, this, &QueryTab::saveAs);
|
||||||
actions.push_back(action);
|
// actions.push_back(action);
|
||||||
// Save copy as
|
// Save copy as
|
||||||
// Copy
|
// Copy
|
||||||
// Copy as C-string
|
// Copy as C-string
|
||||||
|
|
|
||||||
6
pglablib/PgAm.cpp
Normal file
6
pglablib/PgAm.cpp
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#include "PgAm.h"
|
||||||
|
|
||||||
|
PgAm::PgAm()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
19
pglablib/PgAm.h
Normal file
19
pglablib/PgAm.h
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
#ifndef PGAM_H
|
||||||
|
#define PGAM_H
|
||||||
|
|
||||||
|
#include "Pgsql_declare.h"
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
class PgAm {
|
||||||
|
public:
|
||||||
|
Oid oid;
|
||||||
|
QString name;
|
||||||
|
|
||||||
|
PgAm();
|
||||||
|
|
||||||
|
bool operator==(Oid rhs) const { return oid == rhs; }
|
||||||
|
bool operator<(Oid rhs) const { return oid < rhs; }
|
||||||
|
bool operator<(const PgAm &rhs) const { return oid < rhs.oid; }
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PGAM_H
|
||||||
17
pglablib/PgAmContainer.cpp
Normal file
17
pglablib/PgAmContainer.cpp
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
#include "PgAmContainer.h"
|
||||||
|
#include "Pgsql_Col.h"
|
||||||
|
|
||||||
|
std::string PgAmContainer::getLoadQuery() const
|
||||||
|
{
|
||||||
|
std::string q = "SELECT oid, amname FROM pg_am";
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
|
||||||
|
PgAm PgAmContainer::loadElem(const Pgsql::Row &row)
|
||||||
|
{
|
||||||
|
Pgsql::Col col(row);
|
||||||
|
PgAm v;
|
||||||
|
col >> v.oid >> v.name;
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
18
pglablib/PgAmContainer.h
Normal file
18
pglablib/PgAmContainer.h
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#ifndef PGAMCONTAINER_H
|
||||||
|
#define PGAMCONTAINER_H
|
||||||
|
|
||||||
|
#include "PgContainer.h"
|
||||||
|
#include "PgAm.h"
|
||||||
|
#include "Pgsql_declare.h"
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class PgAmContainer : public PgContainer<PgAm> {
|
||||||
|
public:
|
||||||
|
using PgContainer<PgAm>::PgContainer;
|
||||||
|
|
||||||
|
virtual std::string getLoadQuery() const override;
|
||||||
|
protected:
|
||||||
|
virtual PgAm loadElem(const Pgsql::Row &row) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PGAMCONTAINER_H
|
||||||
|
|
@ -95,7 +95,7 @@ protected:
|
||||||
*
|
*
|
||||||
* When overriding this function there is no need to override load.
|
* When overriding this function there is no need to override load.
|
||||||
*/
|
*/
|
||||||
virtual T loadElem(const Pgsql::Row &) { return T(); }
|
virtual T loadElem(const Pgsql::Row &) { return m_invalidInstance; }
|
||||||
private:
|
private:
|
||||||
T m_invalidInstance;
|
T m_invalidInstance;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#include "PgDatabaseCatalog.h"
|
#include "PgDatabaseCatalog.h"
|
||||||
|
|
||||||
#include "ASyncDBConnection.h"
|
#include "ASyncDBConnection.h"
|
||||||
|
#include "PgAmContainer.h"
|
||||||
#include "PgAttributeContainer.h"
|
#include "PgAttributeContainer.h"
|
||||||
#include "PgAuthIdContainer.h"
|
#include "PgAuthIdContainer.h"
|
||||||
#include "PgClassContainer.h"
|
#include "PgClassContainer.h"
|
||||||
|
|
@ -128,32 +129,36 @@ void PgDatabaseCatalog::loadAll(Pgsql::Connection &conn,
|
||||||
std::function<bool(int, int)> progress_callback)
|
std::function<bool(int, int)> progress_callback)
|
||||||
{
|
{
|
||||||
loadInfo(conn);
|
loadInfo(conn);
|
||||||
|
const int count = 10;
|
||||||
int n = 0;
|
int n = 0;
|
||||||
if (progress_callback && !progress_callback(++n, 9))
|
if (progress_callback && !progress_callback(++n, count))
|
||||||
return;
|
return;
|
||||||
load2(m_namespaces, conn);
|
load2(m_namespaces, conn);
|
||||||
if (progress_callback && !progress_callback(++n, 9))
|
if (progress_callback && !progress_callback(++n, count))
|
||||||
return;
|
return;
|
||||||
load2(m_classes, conn); // needs namespaces
|
load2(m_classes, conn); // needs namespaces
|
||||||
if (progress_callback && !progress_callback(++n, 9))
|
if (progress_callback && !progress_callback(++n, count))
|
||||||
return;
|
return;
|
||||||
load2(m_attributes, conn);
|
load2(m_attributes, conn);
|
||||||
if (progress_callback && !progress_callback(++n, 9))
|
if (progress_callback && !progress_callback(++n, count))
|
||||||
return;
|
return;
|
||||||
load2(m_authIds, conn);
|
load2(m_authIds, conn);
|
||||||
if (progress_callback && !progress_callback(++n, 9))
|
if (progress_callback && !progress_callback(++n, count))
|
||||||
return;
|
return;
|
||||||
load2(m_constraints, conn);
|
load2(m_constraints, conn);
|
||||||
if (progress_callback && !progress_callback(++n, 9))
|
if (progress_callback && !progress_callback(++n, count))
|
||||||
return;
|
return;
|
||||||
load2(m_databases, conn);
|
load2(m_databases, conn);
|
||||||
if (progress_callback && !progress_callback(++n, 9))
|
if (progress_callback && !progress_callback(++n, count))
|
||||||
return;
|
return;
|
||||||
load2(m_indexes, conn);
|
load2(m_indexes, conn);
|
||||||
if (progress_callback && !progress_callback(++n, 9))
|
if (progress_callback && !progress_callback(++n, count))
|
||||||
|
return;
|
||||||
|
load2(m_ams, conn);
|
||||||
|
if (progress_callback && !progress_callback(++n, count))
|
||||||
return;
|
return;
|
||||||
load2(m_types, conn);
|
load2(m_types, conn);
|
||||||
progress_callback && progress_callback(++n, 9);
|
progress_callback && progress_callback(++n, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PgDatabaseCatalog::loadInfo(Pgsql::Connection &conn)
|
void PgDatabaseCatalog::loadInfo(Pgsql::Connection &conn)
|
||||||
|
|
@ -230,6 +235,11 @@ std::shared_ptr<const PgIndexContainer> PgDatabaseCatalog::indexes() const
|
||||||
return m_indexes;
|
return m_indexes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<const PgAmContainer> PgDatabaseCatalog::ams() const
|
||||||
|
{
|
||||||
|
return m_ams;
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<const PgNamespaceContainer> PgDatabaseCatalog::namespaces() const
|
std::shared_ptr<const PgNamespaceContainer> PgDatabaseCatalog::namespaces() const
|
||||||
{
|
{
|
||||||
return m_namespaces;
|
return m_namespaces;
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ class PgConstraintContainer;
|
||||||
class PgDatabaseContainer;
|
class PgDatabaseContainer;
|
||||||
class PgIndexContainer;
|
class PgIndexContainer;
|
||||||
class PgNamespaceContainer;
|
class PgNamespaceContainer;
|
||||||
|
class PgAmContainer;
|
||||||
class PgTypeContainer;
|
class PgTypeContainer;
|
||||||
|
|
||||||
class PgDatabaseCatalog: public std::enable_shared_from_this<PgDatabaseCatalog> {
|
class PgDatabaseCatalog: public std::enable_shared_from_this<PgDatabaseCatalog> {
|
||||||
|
|
@ -43,6 +44,7 @@ public:
|
||||||
std::shared_ptr<const PgConstraintContainer> constraints() const;
|
std::shared_ptr<const PgConstraintContainer> constraints() const;
|
||||||
std::shared_ptr<const PgDatabaseContainer> databases() const;
|
std::shared_ptr<const PgDatabaseContainer> databases() const;
|
||||||
std::shared_ptr<const PgIndexContainer> indexes() const;
|
std::shared_ptr<const PgIndexContainer> indexes() const;
|
||||||
|
std::shared_ptr<const PgAmContainer> ams() const;
|
||||||
std::shared_ptr<const PgNamespaceContainer> namespaces() const;
|
std::shared_ptr<const PgNamespaceContainer> namespaces() const;
|
||||||
std::shared_ptr<const PgTypeContainer> types() const;
|
std::shared_ptr<const PgTypeContainer> types() const;
|
||||||
private:
|
private:
|
||||||
|
|
@ -55,6 +57,7 @@ private:
|
||||||
std::shared_ptr<PgConstraintContainer> m_constraints;
|
std::shared_ptr<PgConstraintContainer> m_constraints;
|
||||||
std::shared_ptr<PgDatabaseContainer> m_databases;
|
std::shared_ptr<PgDatabaseContainer> m_databases;
|
||||||
std::shared_ptr<PgIndexContainer> m_indexes;
|
std::shared_ptr<PgIndexContainer> m_indexes;
|
||||||
|
std::shared_ptr<PgAmContainer> m_ams;
|
||||||
std::shared_ptr<PgNamespaceContainer> m_namespaces;
|
std::shared_ptr<PgNamespaceContainer> m_namespaces;
|
||||||
std::shared_ptr<PgTypeContainer> m_types;
|
std::shared_ptr<PgTypeContainer> m_types;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,22 @@
|
||||||
#include "PgIndex.h"
|
#include "PgIndex.h"
|
||||||
|
#include "PgDatabaseCatalog.h"
|
||||||
|
#include "PgClassContainer.h"
|
||||||
|
#include "PgAmContainer.h"
|
||||||
|
|
||||||
PgIndex::PgIndex() = default;
|
PgIndex::PgIndex() = default;
|
||||||
|
|
||||||
|
PgIndex::PgIndex(std::weak_ptr<PgDatabaseCatalog> cat)
|
||||||
|
: PgObject(cat)
|
||||||
|
{}
|
||||||
|
|
||||||
|
QString PgIndex::getAm() const
|
||||||
|
{
|
||||||
|
auto cat = catalog.lock();
|
||||||
|
QString result;
|
||||||
|
if (cat) {
|
||||||
|
auto idxcls = cat->classes()->getByKey(indexrelid);
|
||||||
|
auto am = cat->ams()->getByKey(idxcls.am);
|
||||||
|
result = am.name;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
#ifndef PGINDEX_H
|
#ifndef PGINDEX_H
|
||||||
#define PGINDEX_H
|
#define PGINDEX_H
|
||||||
|
|
||||||
|
#include "PgObject.h"
|
||||||
#include "Pgsql_declare.h"
|
#include "Pgsql_declare.h"
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class PgIndex {
|
class PgIndex : public PgObject {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
Oid indexrelid = InvalidOid;
|
Oid indexrelid = InvalidOid; // oid of pg_class for this index
|
||||||
Oid relid = InvalidOid;
|
Oid relid = InvalidOid; // oid of table (pg_class) where this is an index on
|
||||||
int16_t natts = 0;
|
int16_t natts = 0;
|
||||||
bool isunique = false;
|
bool isunique = false;
|
||||||
bool isprimary = false;
|
bool isprimary = false;
|
||||||
|
|
@ -30,6 +31,9 @@ public:
|
||||||
QString definition;
|
QString definition;
|
||||||
|
|
||||||
PgIndex();
|
PgIndex();
|
||||||
|
explicit PgIndex(std::weak_ptr<PgDatabaseCatalog> cat);
|
||||||
|
|
||||||
|
QString getAm() const;
|
||||||
|
|
||||||
bool operator==(Oid _oid) const { return indexrelid == _oid; }
|
bool operator==(Oid _oid) const { return indexrelid == _oid; }
|
||||||
//bool operator==(const QString &n) const { return name == n; }
|
//bool operator==(const QString &n) const { return name == n; }
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ SELECT indexrelid, indrelid, indnatts, indisunique, indisprimary,
|
||||||
PgIndex PgIndexContainer::loadElem(const Pgsql::Row &row)
|
PgIndex PgIndexContainer::loadElem(const Pgsql::Row &row)
|
||||||
{
|
{
|
||||||
Pgsql::Col col(row);
|
Pgsql::Col col(row);
|
||||||
PgIndex v;
|
PgIndex v(m_catalogue);
|
||||||
col >> v.indexrelid >> v.relid >> v.natts >> v.isunique
|
col >> v.indexrelid >> v.relid >> v.natts >> v.isunique
|
||||||
>> v.isprimary >> v.isexclusion >> v.immediate >> v.isclustered
|
>> v.isprimary >> v.isexclusion >> v.immediate >> v.isclustered
|
||||||
>> v.isvalid >> v.checkxmin >> v.isready >> v.islive;
|
>> v.isvalid >> v.checkxmin >> v.isready >> v.islive;
|
||||||
|
|
|
||||||
6
pglablib/PgObject.cpp
Normal file
6
pglablib/PgObject.cpp
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#include "PgObject.h"
|
||||||
|
|
||||||
|
PgObject::PgObject()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
19
pglablib/PgObject.h
Normal file
19
pglablib/PgObject.h
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
#ifndef PGOBJECT_H
|
||||||
|
#define PGOBJECT_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class PgDatabaseCatalog;
|
||||||
|
|
||||||
|
class PgObject
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PgObject();
|
||||||
|
explicit PgObject(std::weak_ptr<PgDatabaseCatalog> cat)
|
||||||
|
: catalog(cat)
|
||||||
|
{}
|
||||||
|
protected:
|
||||||
|
std::weak_ptr<PgDatabaseCatalog> catalog;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PGOBJECT_H
|
||||||
|
|
@ -52,7 +52,10 @@ SOURCES += \
|
||||||
util.cpp \
|
util.cpp \
|
||||||
SqlFormattingUtils.cpp \
|
SqlFormattingUtils.cpp \
|
||||||
PgKeywordList.cpp \
|
PgKeywordList.cpp \
|
||||||
QueryGenerator.cpp
|
QueryGenerator.cpp \
|
||||||
|
PgAm.cpp \
|
||||||
|
PgAmContainer.cpp \
|
||||||
|
PgObject.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
Pglablib.h \
|
Pglablib.h \
|
||||||
|
|
@ -82,7 +85,10 @@ HEADERS += \
|
||||||
SqlFormattingUtils.h \
|
SqlFormattingUtils.h \
|
||||||
PgCatalogTypes.h \
|
PgCatalogTypes.h \
|
||||||
PgKeywordList.h \
|
PgKeywordList.h \
|
||||||
QueryGenerator.h
|
QueryGenerator.h \
|
||||||
|
PgAm.h \
|
||||||
|
PgAmContainer.h \
|
||||||
|
PgObject.h
|
||||||
|
|
||||||
unix {
|
unix {
|
||||||
target.path = /usr/lib
|
target.path = /usr/lib
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue