Sometimes, it may be necessary to expose data in your flat file with the relational data. There might be some situations where there is no option to load that data in a table. But still, you need a way to use this flat file data in your SQL statements.

DB2 provides the concept of a table function that you can write. Here is one C table UDF example that reads a file and returns that data through your SQL statement.

Download sample code ufd.zip

udfreadfile.c

#include <stdio.h>
#include <stdarg.h>
#include <sqludf.h> 

#define  DEBUG 0

struct SCRATCHDATA
{
  long idx;
  FILE *p;
};


void hardw(char *msg, ...)
{
   FILE *p;
   char name[500];
   va_list argp;

   if (DEBUG) 
   {
      strcpy(name, getenv("HOME"));
      if (name == NULL)
      {
         strcpy(name, "/tmp/hard.txt");
      } else
      {
         strcat(name, "/sqllib/hard.txt");
      }
      p = fopen(name, "a");
      if (p == NULL)
      {
         p = fopen("/tmp/hard.txt", "a");
      }
      if (p != NULL)
      {
         va_start(argp, msg);
         vfprintf(p, msg, argp);
         va_end(argp);
         fprintf(p, "\n");
         fflush( p);
         fclose(p);
      } 
   }
} 

void SQL_API_FN  readFile(SQLUDF_VARCHAR *fileName, 
              SQLUDF_VARCHAR *cm15, 
              SQLUDF_NULLIND *fileNameInd, 
              SQLUDF_NULLIND *cm15IND, 
              SQLUDF_TRAIL_ARGS_ALL)
{
  char myData[100];
  long i = 0;
  char errMsg[1000];
  struct SCRATCHDATA *sp;
  sp = (struct SCRATCHDATA *) SQLUDF_SCRAT->data;

  switch (SQLUDF_CALLT)
  {
  case SQLUDF_TF_FIRST:
      hardw("in SQLUDF_TF_FIRST ******");
      if (*fileNameInd == -1)
      {
         strcpy( SQLUDF_STATE, "38777");
         sprintf (errMsg, "Null file name.");
         strcpy( SQLUDF_MSGTX, errMsg);
         break;
      }
      sp->p = fopen(fileName, "r");
      if (sp->p == NULL)
      {
         strcpy( SQLUDF_STATE, "38777");
         sprintf (errMsg, "Given file can not be opened", fileName);
         strcpy( SQLUDF_MSGTX, errMsg);
         hardw("File '%s'can not be opened", fileName);
         break;
      }
      sp->idx = 0;
    case SQLUDF_TF_OPEN:
      hardw("in SQLUDF_TF_OPEN ******");
      break;
    case SQLUDF_TF_FETCH:
      hardw("in SQLUDF_TF_FETCH %ld ******", sp->idx);
      if (fgets(myData, 100, sp->p) == NULL)
      {
         strcpy( SQLUDF_STATE, "02000");
         break;
      }
      myData[strlen(myData)-1] = '\0';
      strcpy(cm15, myData);
      *cm15IND = 0;
      sp->idx++;
      break;
    case SQLUDF_TF_CLOSE:
       hardw("in SQLUDF_TF_CLOSE ******");
       fclose(sp->p);
      break;
    case SQLUDF_TF_FINAL:
      /* close the file */
      hardw("in SQLUDF_TF_FINAL ******");
      sp->idx = 0;
      break;
  }
}

udfreadfile.exp

readFile

How to catalog Table UDF (udfcat)

The catalog definition to register C table UDF in DB2 catalog is given below:

#! /bin/ksh
export ${FENCED:="FENCED NOT THREADSAFE"}
export ${SHLIBNAME:="udfreadfile"}
TOK=$(date +"%y%m%d%H%M%S")
export SHLIBNAME=udfreadfile$TOK
rm -f ~/sqllib/function/udfreadfile*
cp -f udfreadfile ~/sqllib/function/$SHLIBNAME

db2 -tv <<!EOF

CONNECT TO $1;

DROP FUNCTION READ_FILE;

CREATE FUNCTION READ_FILE
(
  FILE_NAME       VARCHAR(128)
)
RETURNS TABLE (c1 varchar(15))
EXTERNAL NAME '${SHLIBNAME}!readFile'
SPECIFIC READFILE
LANGUAGE C
PARAMETER STYLE DB2SQL
${FENCED}
RETURNS NULL ON NULL INPUT
NOT DETERMINISTIC
NO SQL
NO EXTERNAL ACTION
SCRATCHPAD
FINAL CALL
DISALLOW PARALLEL;

The above is a script so make sure you set execute bit on it. i.e. chmod +x udfcat

How to compile?

On your platform, copy bldrtn from your ~/sqllib/sample/c dir to your local directory and use this makefile to compile this program.

DB2PATH=${HOME}/sqllib

BLDRTN=./bldrtn

ALIAS=sample
UID=
PWD=

COPY=cp
ERASE=rm -f

all : \
	srv

srv : \
	rtn

rtn : \
	udfreadfile

clean : 
	$(ERASE) *.o

#****************************************************************************
#		   2h - make cleanall
#****************************************************************************

cleanall : \
	clean
	$(ERASE) udfreadfile
	$(ERASE) $(DB2PATH)/function/udfreadfile

udfreadfile : udfreadfile.c
	$(BLDRTN) udfreadfile $(ALIAS)
	./udfcat $(ALIAS) > udfcat.log

How this UDF works?

The sample code given in this UDF reads a flat file having a column of data. The size of the data is 15 character and file can have as many rows as possible.

The UDF defines a scratchpad memory area where we store the file pointer so that it can be used for each row. When this UDF is executed for each row, the different segment of code are executed in certain order.

For example:

SQLUDF_TF_FIRST is used to initialize variables or open a file pointer. This will be executed only once.
SQLUDF_TF_FETCH is used for each row execution. Remember, there is no while loop to go through the contents of the file since different code segments are used for different events.
Use SQLUDF_TF_CLOSE or SQLUDF_TF_FINAL to free up file pointer or free up memory.

FINAL CALL is used in UDF definition so that DB2 can invoke calls for SQLUDF_TF_FIRST and SQLUDF_TF_FINAL code segments. SCRATCHPAD is used to store file pointer and make sure that the size of the SCRATCHPAD is sufficient enough to hold the structure. The default size is only 100 bytes.

How to invoke the table UDF?

SELECT * FROM TABLE (db2inst1.read_file('$HOME/tableudf/datafilename.txt'))

You can use this UDF in your query to join it with other tables etc or use it in a sub-query.

UDF to show database directory

Date: May 27, 2025

Here is a table UDF that lists the database directory using Db2 API call.

File Name: listdb.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sqludf.h>
#include <db2ApiDf.h>

#ifdef __cplusplus
extern "C" {
#endif

struct SCRATCHDATA
{
FILE *debug_fp;
int done; /* Flag to indicate if processing is done */
int scan_id; /* Persistent scan ID */
int num_entries; /* Number of entries */
int current_entry; /* Current entry index */
};

void SQL_API_FN list_db(
SQLUDF_VARCHAR *alias_name, /* Output: Database alias */
SQLUDF_VARCHAR *db_name, /* Output: Database name */
SQLUDF_NULLIND *alias_name_ind, /* Null indicator for alias_name */
SQLUDF_NULLIND *db_name_ind, /* Null indicator for db_name */
SQLUDF_TRAIL_ARGS_ALL) /* Standard table function arguments */
{
struct db2DbDirInfoV9 *db_entry = NULL; /* Structure for database directory entry */
db2DbDirOpenScanStruct open_struct; /* Structure for opening directory scan */
db2DbDirNextEntryStructV9 next_struct; /* Structure for getting next entry */
db2DbDirCloseScanStruct close_struct; /* Structure for closing directory scan */
struct SCRATCHDATA *sp;
struct sqlca sqlca; /* SQLCA for error handling */
char sqlstate[6] = "00000"; /* SQL state for errors */
int rc = 0; /* Return code */

sp = (struct SCRATCHDATA *)SQLUDF_SCRAT->data;

switch (SQLUDF_CALLT)
{
/* Initialize Scratchpad */
case SQLUDF_TF_FIRST:
sp->scan_id = 0;
sp->current_entry = 0;
sp->done = 0;
sp->debug_fp = fopen("/tmp/list_db.log", "a");
break;

case SQLUDF_TF_OPEN:
open_struct.oHandle = 0; /* Initialize handle */
open_struct.piPath = NULL;
rc = db2DbDirOpenScan(db2Version970, &open_struct, &sqlca);
if (rc != 0 || sqlca.sqlcode != 0) {
if (sp->debug_fp != NULL) {
fprintf(sp->debug_fp, "Failed to open database directory scan: RC=%d, SQLCODE=%d\n", rc, sqlca.sqlcode);
}
strcpy(sqlstate, "38999");
strcpy(SQLUDF_MSGTX, "Failed to open database directory scan");
strncpy(SQLUDF_STATE, sqlstate, 5);
sp->done = 1;
} else {
sp->scan_id = open_struct.oHandle; /* Store handle */
sp->num_entries = open_struct.oNumEntries; /* Store number of entries */
if (sp->debug_fp != NULL) {
fprintf(sp->debug_fp, "Opened database directory scan: Handle=%d, num_entries=%d\n", sp->scan_id, sp->num_entries);
}
}
break;

case SQLUDF_TF_FETCH:
if (sp->done) {
/* No more entries - exit FETCH mode */
strcpy( SQLUDF_STATE, "02000");
break;
}
next_struct.iHandle = sp->scan_id;
/* Get database entries */
rc = db2DbDirGetNextEntry(db2Version970, &next_struct, &sqlca);
if (rc != 0 || sqlca.sqlcode != 0) {
strcpy(sqlstate, "38999");
strcpy(SQLUDF_MSGTX, "No more entries in database directory");
strncpy(SQLUDF_STATE, sqlstate, 5);
sp->done = 1;
break;
} else {
/* Database name and alias found - set the output var*/
db_entry = next_struct.poDbDirEntry;
strncpy(alias_name, db_entry->alias, 8);
alias_name[8] = '\0';
*alias_name_ind = 0;
strncpy(db_name, db_entry->dbname, 8);
db_name[8] = '\0';
*db_name_ind = 0;
if (sp->debug_fp != NULL) {
fprintf(sp->debug_fp, "Fetched database entry: alias=%s, dbname=%s\n", alias_name, db_name);
}
}
++sp->current_entry;
if (sp->current_entry >= sp->num_entries) {
/* No more entries - set done flag */
sp->done = 1;
}
break;

case SQLUDF_TF_CLOSE:
/* Close the database directory scan */
close_struct.iHandle = sp->scan_id;
rc = db2DbDirCloseScan(db2Version970, &close_struct, &sqlca);
if (rc != 0 || sqlca.sqlcode != 0) {
strcpy(sqlstate, "38999");
strcpy(SQLUDF_MSGTX, "Failed to close database directory scan");
strncpy(SQLUDF_STATE, sqlstate, 5);
if (sp->debug_fp != NULL) {
fprintf(sp->debug_fp, "Failed to close database directory scan\n");
}
}
if (sp->debug_fp != NULL) {
fclose(sp->debug_fp);
}
break;
default:
break;
}
return;
}

#ifdef __cplusplus
}
#endif%


Makefile

# Db2 installation directory (override with DB2_HOME environment variable or set manually)
DB2_HOME ?= $HOME/sqllib

# Compiler and flags
CC = gcc
CFLAGS = -g -fpic -Wall -I$(DB2_HOME)/include -D_REENTRANT
LDFLAGS = -shared -L$(DB2_HOME)/lib -ldb2 -lpthread

# Target library name
TARGET = udflib

# Source file rn

SRC = listdb.c

# Default target
all: $(TARGET)

# Build the shared library
$(TARGET): $(SRC)
	$(CC) $(CFLAGS) $(SRC) -o $@ $(LDFLAGS)

# Install the library to DB2 function directory
install: $(TARGET)
	./create_fun.sh

# Clean up
clean:
	rm -f $(TARGET)

# Phony targets
.PHONY: all clean install

Register Function

#!/bin/bash
export ${FENCED:="FENCED NOT THREADSAFE"}
TOK=$(date +"%y%m%d%H%M%S")
export SHLIBNAME=udflib$TOK
rm -f ~/sqllib/function/udflib*
cp -f udflib ~/sqllib/function/$SHLIBNAME
db2 CONNECT TO VIZ
db2 -tv <<EOF

CREATE OR REPLACE FUNCTION list_db()
     RETURNS TABLE (
       db_name VARCHAR(128),
       alias_name VARCHAR(128)
     )
EXTERNAL NAME '${SHLIBNAME}!list_db'
SPECIFIC list_db
LANGUAGE C
PARAMETER STYLE DB2SQL
NO SQL
${FENCED}
NO EXTERNAL ACTION
SCRATCHPAD FINAL CALL
NOT DETERMINISTIC
DISALLOW PARALLEL
;
CONNECT RESET;
EOF%

How to call

select * from table (list_db())