Search code examples
cpostgresqlpostgresql-extensions

how to check table's column's data type of postgresql using c?


I am working on creating a postgresql extension, till now I only wanted to test if one of table's columns is bytea type to store the table's name , but when I test it with :

CREATE TABLE test_table (
    id SERIAL PRIMARY KEY,
    data bytea
);

it says that no bytea columns found in table...

here's my c code :

#include "postgres.h"
#include "fmgr.h"
#include "commands/event_trigger.h"
#include "parser/parse_node.h"
#include "executor/spi.h"
#include "utils/builtins.h"
#include "catalog/pg_type.h"
#include "nodes/pg_list.h"

PG_MODULE_MAGIC;

PG_FUNCTION_INFO_V1(log_table_creation);

Datum
log_table_creation(PG_FUNCTION_ARGS)
{
    EventTriggerData *trigdata;
    const char *tag;
    int ret;

    if (!CALLED_AS_EVENT_TRIGGER(fcinfo))
        elog(ERROR, "not fired by event trigger manager");

    trigdata = (EventTriggerData *) fcinfo->context;
    tag = GetCommandTagName(trigdata->tag);

    if (strcmp(tag, "CREATE TABLE") != 0)
        PG_RETURN_NULL();

    // Cast parsetree to CreateStmt to access the table structure
    CreateStmt *createStmt = (CreateStmt *) trigdata->parsetree;
    RangeVar *relation = createStmt->relation;

    // Check if any column has type `bytea`
    bool has_bytea_column = false;
    ListCell *cell;

    foreach(cell, createStmt->tableElts)
    {
        ColumnDef *colDef = (ColumnDef *) lfirst(cell);

        // Check for type name "bytea" explicitly
        if (list_length(colDef->typeName->names) == 2) // {"pg_catalog", "bytea"}
        {
            // Extract schema and type as strings
            const char *schema = strVal(linitial(colDef->typeName->names));
            const char *type = strVal(lsecond(colDef->typeName->names));

            if (strcmp(schema, "pg_catalog") == 0 && strcmp(type, "bytea") == 0)
            {
                has_bytea_column = true;
                break;
            }
        }
    }

    // Only log the table if it has a `bytea` column
    if (has_bytea_column)
    {
        // Prepare and execute the insertion into table_log
        SPI_connect();
        char *query = psprintf("INSERT INTO table_log (table_name) VALUES ('%s')", relation->relname);
        ret = SPI_execute(query, false, 0);
        SPI_finish();

        if (ret != SPI_OK_INSERT)
            elog(ERROR, "Failed to insert into table_log");
    }
    else
    {
        elog(NOTICE, "No BYTEA columns found in table %s", relation->relname);
    }

    PG_RETURN_NULL();
}

and her's the sql script:

CREATE TABLE table_log (
id SERIAL PRIMARY KEY,
table_name TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE FUNCTION log_table_creation()
RETURNS event_trigger
LANGUAGE c
AS 'MODULE_PATHNAME', 'log_table_creation';

CREATE EVENT TRIGGER table_creation_logger
ON ddl_command_end
WHEN TAG IN ('CREATE TABLE')
EXECUTE FUNCTION log_table_creation();

it all new to me,so if anyone could help me with it, and if there's tutorials it would be the best. thank you


Solution

  • this is how i fixed it

    // Event trigger function to log tables created with a bytea column
    Datum
    log_table_creation(PG_FUNCTION_ARGS)
    {
    EventTriggerData *trigdata;
    const char *tag;
    int ret;
    
    if (!CALLED_AS_EVENT_TRIGGER(fcinfo))
        elog(ERROR, "not fired by event trigger manager");
    
    trigdata = (EventTriggerData *) fcinfo->context;
    tag = GetCommandTagName(trigdata->tag);
    
    if (strcmp(tag, "CREATE TABLE") != 0)
        PG_RETURN_NULL();
    
    // Cast parsetree to CreateStmt to access the table structure
    CreateStmt *createStmt = (CreateStmt *) trigdata->parsetree;
    RangeVar *relation = createStmt->relation;
    
    // Check if any column has type `bytea`
    bool has_bytea_column = false;
    ListCell *cell;
    
    foreach(cell, createStmt->tableElts)
    {
        ColumnDef *colDef = (ColumnDef *) lfirst(cell);
    
        // Retrieve the type OID directly
        Oid typeOid = typenameTypeId(NULL, colDef->typeName);
    
        // Log the OID of each column's type for debugging
        elog(INFO, "Column %s has type OID: %u", colDef->colname, typeOid);
    
        // Check if the type OID matches `BYTEAOID`
        if (typeOid == BYTEAOID)
        {
            has_bytea_column = true;
            break;
        }
    }
    
    // Only log the table if it has a `bytea` column
    if (has_bytea_column)
    {
        SPI_connect();
        char *query = psprintf("INSERT INTO table_log (table_name) VALUES ('%s')", relation->relname);
        ret = SPI_execute(query, false, 0);
        SPI_finish();
    
        if (ret != SPI_OK_INSERT)
            elog(ERROR, "Failed to insert into table_log");
        else
            elog(INFO, "Table %s with BYTEA column logged successfully", relation->relname);
    }
    else
    {
        elog(NOTICE, "No BYTEA columns found in table %s", relation->relname);
    }
    
    PG_RETURN_NULL();
    }