Skip to content

Commit ea66ac2

Browse files
committed
sql: add schema descriptions as table and column comments
The steampipe CLI does this, and it's very helpful when trying to figure out what a specific table or column holds.
1 parent b7dc535 commit ea66ac2

File tree

7 files changed

+166
-3
lines changed

7 files changed

+166
-3
lines changed

fdw.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,5 +653,39 @@ func goFdwValidate(coid C.Oid, opts *C.List) {
653653
// or a required option is missing.
654654
}
655655

656+
//export goFdwGetForeignTableComments
657+
func goFdwGetForeignTableComments(servername, schemaname, tablename *C.char) *C.List {
658+
remoteSchema := C.GoString(servername)
659+
localSchema := C.GoString(schemaname)
660+
tableName := C.GoString(tablename)
661+
log.Printf("[TRACE] goFdwGetForeignTableComments, serverName: %s, localSchema: %s, tableName: %s", remoteSchema, localSchema, tableName)
662+
663+
// get the plugin hub,
664+
pluginHub := hub.GetHub()
665+
666+
var sql *C.List
667+
668+
// special handling for the command schema
669+
if remoteSchema == constants.InternalSchema {
670+
log.Printf("[INFO] getting comments for setting tables into %s", remoteSchema)
671+
settingsSchema := pluginHub.GetSettingsSchema()
672+
sql = SchemaToCommentsSql(localSchema, tableName, settingsSchema[tableName])
673+
} else if remoteSchema == constants.LegacyCommandSchema {
674+
log.Printf("[INFO] getting comments for setting tables into %s", remoteSchema)
675+
settingsSchema := pluginHub.GetLegacySettingsSchema()
676+
sql = SchemaToCommentsSql(localSchema, tableName, settingsSchema[tableName])
677+
} else {
678+
schema, err := pluginHub.GetSchema(remoteSchema, localSchema)
679+
if err != nil {
680+
log.Printf("[WARN] goFdwGetForeignTableComments failed: %s", err)
681+
FdwError(err)
682+
return nil
683+
}
684+
sql = SchemaToCommentsSql(localSchema, tableName, schema.Schema[tableName])
685+
}
686+
687+
return sql
688+
}
689+
656690
// required by buildmode=c-archive
657691
func main() {}

fdw/common.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "nodes/bitmapset.h"
1414
#include "nodes/makefuncs.h"
1515
#include "nodes/pg_list.h"
16+
#include "tcop/utility.h"
1617
#include "utils/builtins.h"
1718
#include "utils/inet.h"
1819
#include "utils/jsonb.h"
@@ -133,4 +134,4 @@ List *deserializeDeparsedSortGroup(List *items);
133134
OpExpr *canonicalOpExpr(OpExpr *opExpr, Relids base_relids);
134135
ScalarArrayOpExpr *canonicalScalarArrayOpExpr(ScalarArrayOpExpr *opExpr, Relids base_relids);
135136
char *getOperatorString(Oid opoid);
136-
#endif // FDW_COMMON_H
137+
#endif // FDW_COMMON_H

fdw/fdw.c

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,23 @@ static void exitHook(int code, Datum arg);
1616

1717
void *serializePlanState(FdwPlanState *state);
1818
FdwExecState *initializeExecState(void *internalstate);
19+
20+
static void
21+
steampipe_fdw_ProcessUtility(PlannedStmt *pstmt,
22+
const char *queryString,
23+
bool readOnlyTree,
24+
ProcessUtilityContext context,
25+
ParamListInfo params,
26+
QueryEnvironment *queryEnv,
27+
DestReceiver *dest,
28+
QueryCompletion *qc);
29+
1930
// Required by postgres, doing basic checks to ensure compatibility,
2031
// such as being compiled against the correct major version.
2132
PG_MODULE_MAGIC;
2233

34+
static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
35+
2336
// Define the handler function for signal 16
2437
void signal_handler(int sig) {
2538
// elog(NOTICE, "Caught signal %d", sig);
@@ -63,6 +76,9 @@ void _PG_init(void)
6376
on_proc_exit(&exitHook, PointerGetDatum(NULL));
6477
RegisterXactCallback(pgfdw_xact_callback, NULL);
6578

79+
/* Hook ProcessUtility for adding comments to foreign tables */
80+
next_ProcessUtility_hook = ProcessUtility_hook;
81+
ProcessUtility_hook = steampipe_fdw_ProcessUtility;
6682
}
6783

6884
/*
@@ -88,6 +104,71 @@ void exitHook(int code, Datum arg)
88104
goFdwShutdown();
89105
}
90106

107+
static void
108+
steampipe_fdw_ProcessUtility(PlannedStmt *pstmt,
109+
const char *queryString,
110+
bool readOnlyTree,
111+
ProcessUtilityContext context,
112+
ParamListInfo params,
113+
QueryEnvironment *queryEnv,
114+
DestReceiver *dest,
115+
QueryCompletion *qc) {
116+
List *cmd_list = NULL;
117+
if (IsA(pstmt->utilityStmt, CreateForeignTableStmt) && context == PROCESS_UTILITY_SUBCOMMAND && dest == None_Receiver) {
118+
const CreateForeignTableStmt *cstmt = (const CreateForeignTableStmt *)pstmt->utilityStmt;
119+
ForeignServer *server = GetForeignServerByName(cstmt->servername, true);
120+
if (server != NULL) {
121+
ForeignDataWrapper *wrapper = GetForeignDataWrapper(server->fdwid);
122+
if (wrapper != NULL && wrapper->fdwname != NULL && strcmp(wrapper->fdwname, STEAMPIPE_DATAWRAPPER_NAME) == 0) {
123+
cmd_list = goFdwGetForeignTableComments(cstmt->servername, cstmt->base.relation->schemaname, cstmt->base.relation->relname);
124+
}
125+
}
126+
}
127+
128+
if (next_ProcessUtility_hook) {
129+
next_ProcessUtility_hook(pstmt, queryString, readOnlyTree, context, params, queryEnv, dest, qc);
130+
} else {
131+
standard_ProcessUtility(pstmt, queryString, readOnlyTree, context, params, queryEnv, dest, qc);
132+
}
133+
134+
if (cmd_list != NULL) {
135+
ListCell *lc;
136+
foreach(lc, cmd_list) {
137+
char *cmd = (char *)lfirst(lc);
138+
List *raw_parsetree_list = pg_parse_query(cmd);
139+
140+
ListCell *lc2;
141+
foreach(lc2, raw_parsetree_list) {
142+
RawStmt *rs = lfirst_node(RawStmt, lc2);
143+
CommentStmt *comment_stmt = (CommentStmt *)rs->stmt;
144+
145+
if (!IsA(comment_stmt, CommentStmt)) {
146+
elog(ERROR, "unexpected statement type %d where CommentStmt expected", (int)nodeTag(comment_stmt));
147+
}
148+
149+
#if PG_VERSION_NUM < 120000
150+
// Be sure to advance the command counter between subcommands
151+
// Not needed in PG 12+ because standard_ProcessUtility already does this
152+
CommandCounterIncrement();
153+
#endif
154+
155+
pstmt = makeNode(PlannedStmt);
156+
pstmt->commandType = CMD_UTILITY;
157+
pstmt->canSetTag = false;
158+
pstmt->utilityStmt = (Node *) comment_stmt;
159+
pstmt->stmt_location = rs->stmt_location;
160+
pstmt->stmt_len = rs->stmt_len;
161+
162+
/* Execute statement */
163+
ProcessUtility(pstmt, cmd, false,
164+
PROCESS_UTILITY_SUBCOMMAND, NULL, NULL,
165+
None_Receiver, NULL);
166+
}
167+
}
168+
169+
}
170+
}
171+
91172
static bool fdwIsForeignScanParallelSafe(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) {
92173
return getenv("STEAMPIPE_FDW_PARALLEL_SAFE") != NULL;
93174
}

fdw/fdw_handlers.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// defined and available.
33
#include "fmgr.h"
44

5+
#define STEAMPIPE_DATAWRAPPER_NAME "steampipe_postgres_fdw"
6+
57
static bool fdwIsForeignScanParallelSafe(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte);
68
static void fdwGetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid);
79
static void fdwGetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid);
@@ -44,4 +46,4 @@ Datum fdw_validator(PG_FUNCTION_ARGS) {
4446
List *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
4547
goFdwValidate(catalog, options_list);
4648
PG_RETURN_VOID();
47-
}
49+
}

schema.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,25 @@ func SchemaToSql(schema map[string]*proto.TableSchema, stmt *C.ImportForeignSche
7171

7272
return commands
7373
}
74+
75+
func SchemaToCommentsSql(localSchema, table string, tableSchema *proto.TableSchema) *C.List {
76+
if tableSchema == nil {
77+
return nil
78+
}
79+
80+
log.Printf("[TRACE] Getting comments for table %s", table)
81+
82+
comments, err := sql.GetCommentsForTable(table, tableSchema, localSchema)
83+
if err != nil {
84+
FdwError(err)
85+
return nil
86+
}
87+
88+
var commands *C.List
89+
for _, c := range comments {
90+
log.Printf("[TRACE] SQL %s", c)
91+
commands = C.lappend(commands, unsafe.Pointer(C.CString(c)))
92+
}
93+
94+
return commands
95+
}

sql/sql.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,27 @@ SERVER %s OPTIONS (table %s)`,
4545
return sql, nil
4646
}
4747

48+
func GetCommentsForTable(table string, tableSchema *proto.TableSchema, localSchema string) ([]string, error) {
49+
localSchema = db_common.PgEscapeName(localSchema)
50+
table = db_common.PgEscapeName(table)
51+
52+
var commentStatements []string
53+
if tableSchema.Description != "" {
54+
tableDescription := db_common.PgEscapeString(tableSchema.Description)
55+
commentStatements = append(commentStatements, fmt.Sprintf("COMMENT ON FOREIGN TABLE %s.%s IS %s", localSchema, table, tableDescription))
56+
}
57+
58+
for _, c := range tableSchema.Columns {
59+
if c.Description != "" {
60+
column := db_common.PgEscapeName(c.Name)
61+
columnDescription := db_common.PgEscapeString(c.Description)
62+
commentStatements = append(commentStatements, fmt.Sprintf("COMMENT ON COLUMN %s.%s.%s IS %s", localSchema, table, column, columnDescription))
63+
}
64+
}
65+
66+
return commentStatements, nil
67+
}
68+
4869
func sqlTypeForColumnType(columnType proto.ColumnType) (string, error) {
4970
switch columnType {
5071
case proto.ColumnType_BOOL:

templates/fdw/fdw_handlers.h.tmpl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// defined and available.
33
#include "fmgr.h"
44

5+
#define STEAMPIPE_DATAWRAPPER_NAME "steampipe_postgres_{{.Plugin}}"
6+
57
static void fdwGetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid);
68
static void fdwGetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid);
79
static ForeignScan *fdwGetForeignPlan(
@@ -42,4 +44,4 @@ Datum steampipe_{{.Plugin}}_fdw_validator(PG_FUNCTION_ARGS) {
4244
List *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
4345
goFdwValidate(catalog, options_list);
4446
PG_RETURN_VOID();
45-
}
47+
}

0 commit comments

Comments
 (0)