Skip to content

Commit cf4928c

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 cf4928c

File tree

5 files changed

+154
-1
lines changed

5 files changed

+154
-1
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: 75 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,65 @@ 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+
cmd_list = goFdwGetForeignTableComments(cstmt->servername, cstmt->base.relation->schemaname, cstmt->base.relation->relname);
120+
}
121+
122+
if (next_ProcessUtility_hook) {
123+
next_ProcessUtility_hook(pstmt, queryString, readOnlyTree, context, params, queryEnv, dest, qc);
124+
} else {
125+
standard_ProcessUtility(pstmt, queryString, readOnlyTree, context, params, queryEnv, dest, qc);
126+
}
127+
128+
if (cmd_list != NULL) {
129+
ListCell *lc;
130+
foreach(lc, cmd_list) {
131+
char *cmd = (char *)lfirst(lc);
132+
List *raw_parsetree_list = pg_parse_query(cmd);
133+
134+
ListCell *lc2;
135+
foreach(lc2, raw_parsetree_list) {
136+
RawStmt *rs = lfirst_node(RawStmt, lc2);
137+
CommentStmt *comment_stmt = (CommentStmt *)rs->stmt;
138+
139+
if (!IsA(comment_stmt, CommentStmt)) {
140+
elog(ERROR, "unexpected statement type %d where CommentStmt expected", (int)nodeTag(comment_stmt));
141+
}
142+
143+
#if PG_VERSION_NUM < 120000
144+
// Be sure to advance the command counter between subcommands
145+
// Not needed in PG 12+ because standard_ProcessUtility already does this
146+
CommandCounterIncrement();
147+
#endif
148+
149+
pstmt = makeNode(PlannedStmt);
150+
pstmt->commandType = CMD_UTILITY;
151+
pstmt->canSetTag = false;
152+
pstmt->utilityStmt = (Node *) comment_stmt;
153+
pstmt->stmt_location = rs->stmt_location;
154+
pstmt->stmt_len = rs->stmt_len;
155+
156+
/* Execute statement */
157+
ProcessUtility(pstmt, cmd, false,
158+
PROCESS_UTILITY_SUBCOMMAND, NULL, NULL,
159+
None_Receiver, NULL);
160+
}
161+
}
162+
163+
}
164+
}
165+
91166
static bool fdwIsForeignScanParallelSafe(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) {
92167
return getenv("STEAMPIPE_FDW_PARALLEL_SAFE") != NULL;
93168
}

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+
escapedTableName := 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, escapedTableName, 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, escapedTableName, 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:

0 commit comments

Comments
 (0)