Overview
OmniView uses CGO (C-Go interoperability) to integrate with Oracle Database through the ODPI-C library. This allows Go code to call native C functions for high-performance database operations, particularly for Oracle Advanced Queuing (AQ).
Architecture
High-Level
File Structure
┌─────────────────────────────────────┐
│ OmniView (Go) │
│ │
│ ┌───────────────────────────────┐ │
│ │ Storage Adapter (Go) │ │
│ └──────────┬────────────────────┘ │
│ │ CGO │
│ ┌──────────▼────────────────────┐ │
│ │ dequeue_ops.c (C) │ │
│ └──────────┬────────────────────┘ │
│ │ │
│ ┌──────────▼────────────────────┐ │
│ │ ODPI-C Library │ │
│ └──────────┬────────────────────┘ │
└─────────────┼──────────────────────┘
│
┌─────────────▼──────────────────────┐
│ Oracle Instant Client │
└─────────────┬──────────────────────┘
│
┌─────────────▼──────────────────────┐
│ Oracle Database │
└────────────────────────────────────┘
OmniView/
├── internal/adapter/storage/oracle/
│ ├── oracle.go # Go interface
│ ├── dequeue_ops.c # CGO C implementation (329 lines)
│ └── dequeue_ops.h # C header file
├── third_party/odpi/
│ ├── include/
│ │ ├── dpi.h # ODPI-C main header
│ │ └── dpi_go_helpers.h # CGO helper functions
│ └── lib/
│ ├── libodpi.dylib # macOS shared library
│ └── odpi.dll # Windows DLL
└── Makefile # Build configuration
How OmniView Uses CGO
Purpose
OmniView uses CGO specifically for:
Oracle AQ Dequeue Operations - Efficiently dequeue messages from Oracle Advanced Queuing
CLOB/BLOB Handling - Read large object data from Oracle
PL/SQL Procedure Calls - Execute stored procedures with complex type mappings
Performance - Direct C library calls are faster than pure Go database/sql for bulk operations
Key Files
dequeue_ops.c 329-line C implementation with:
DequeueManyAndExtract() - Main dequeue function
ExecuteDequeueProc() - PL/SQL procedure caller
ReadLobContent() - LOB reader
FreeDequeueResults() - Memory cleanup
dequeue_ops.h 24-line header with:
TraceMessage struct
TraceId struct
Function declarations
CGO Compiler Configuration
CGO Flags from Makefile
The Makefile exports CGO flags that Go automatically uses during compilation:
export CGO_CFLAGS = -I $( PWD ) /third_party/odpi/include
export CGO_LDFLAGS = -L $( PWD ) /third_party/odpi/lib -lodpi \
-Wl,-rpath, $( PWD ) /third_party/odpi/lib \
-Wl,-rpath, $( INSTANT_CLIENT_DIR )
Breakdown:
-I$(PWD)/third_party/odpi/include - Include path for dpi.h
-L$(PWD)/third_party/odpi/lib - Library search path
-lodpi - Link against libodpi.dylib
-Wl,-rpath,... - Runtime library search paths (dylib locations)
export CGO_CFLAGS = -I $( PWD ) /third_party/odpi/include
export CGO_LDFLAGS = -L $( PWD ) /third_party/odpi/lib -lodpi \
-L $( INSTANT_CLIENT_DIR ) -loci
Breakdown:
-I$(PWD)/third_party/odpi/include - Include path for dpi.h
-L$(PWD)/third_party/odpi/lib - Link to odpi.dll
-lodpi - Link against ODPI library
-L$(INSTANT_CLIENT_DIR) -loci - Link Oracle Instant Client OCI library
export CGO_CFLAGS = -I $( PWD ) /third_party/odpi/include
export CGO_LDFLAGS = -L $( PWD ) /third_party/odpi/lib -lodpi \
-Wl,-rpath, $( PWD ) /third_party/odpi/lib
Breakdown:
-I$(PWD)/third_party/odpi/include - Include path
-L$(PWD)/third_party/odpi/lib - Library path
-lodpi - Link against libodpi.so
-Wl,-rpath,... - Runtime library search path
Manual CGO Flag Override
You can manually override CGO flags:
export CGO_CFLAGS = "-I/custom/path/include"
export CGO_LDFLAGS = "-L/custom/path/lib -lodpi"
make build
Manual overrides will be used instead of Makefile defaults. Ensure all required paths and flags are included.
ODPI-C Library Integration
What is ODPI-C?
ODPI-C (Oracle Database Programming Interface for C) is Oracle’s modern C API for database access. It provides:
Simplified API compared to OCI (Oracle Call Interface)
High-performance native database access
Support for all Oracle data types including objects, collections, LOBs
Advanced Queuing (AQ) support
Building the ODPI Library
The ODPI library must be built before compiling OmniView:
ODPI build process details
The make odpi target:
Compiles source files :
gcc -O2 -Wall -I/opt/oracle/instantclient_23_7/sdk/include \
-arch arm64 -c third_party/odpi/src/ * .c
Links into shared library :
macOS:
gcc -dynamiclib -arch arm64 \
-install_name @rpath/libodpi.dylib \
-Wl,-rpath,/opt/oracle/instantclient_23_7 \
-o third_party/odpi/lib/libodpi.dylib \
third_party/odpi/build/ * .o \
-L/opt/oracle/instantclient_23_7 -lclntsh
Windows:
gcc -shared \
-o third_party/odpi/lib/odpi.dll \
third_party/odpi/build/ * .o \
-LC:/oracle_inst/instantclient_23_7 -loci
Copies to output location :
macOS: third_party/odpi/lib/libodpi.dylib
Windows: third_party/odpi/lib/odpi.dll (also copied to workspace root)
ODPI Directory Structure
third_party/odpi/
├── include/
│ ├── dpi.h # Main ODPI-C header (1000+ declarations)
│ ├── dpi_go_helpers.h # Helper macros for Go integration
│ └── dpi_go_helpers.c # Helper function implementations
├── lib/
│ ├── libodpi.dylib # macOS ARM64 shared library
│ └── odpi.dll # Windows DLL
└── build/ # Temporary object files (*.o)
dequeue_ops.c Implementation
The core CGO implementation provides Oracle AQ dequeue functionality.
Data Structures
From dequeue_ops.h:
typedef struct {
char * data; // Message payload (JSON from CLOB)
uint64_t length; // Payload length in bytes
} TraceMessage;
typedef struct {
char * data; // Message ID (RAW bytes)
uint32_t length; // ID length
} TraceId;
Main Function: DequeueManyAndExtract
Signature:
int DequeueManyAndExtract (
dpiConn * conn , // Oracle connection
dpiContext * context , // ODPI context for error handling
const char * subscriberName , // Queue consumer name
uint32_t batchSize , // Number of messages to dequeue
int32_t waitTime , // Wait time: -1=forever, 0=no wait, >0=seconds
TraceMessage ** outMessages , // Output: array of messages
TraceId ** outIds , // Output: array of message IDs
uint32_t * actualCount // Output: actual number dequeued
);
Process flow:
1. Load Oracle object types (OMNI_TRACER_PAYLOAD_ARRAY, OMNI_TRACER_RAW_ARRAY)
2. Create ODPI variables for output collections
3. Call ExecuteDequeueProc() to run PL/SQL procedure
4. Allocate memory for results
5. Iterate through payload collection:
- Extract CLOB attribute from each message object
- Read CLOB content with ReadLobContent()
- Store in TraceMessage struct
6. Iterate through raw ID collection:
- Extract RAW bytes
- Store in TraceId struct
7. Return results or cleanup on error
Code walkthrough (lines 174-307)
int DequeueManyAndExtract (...) {
// Load Oracle object types
dpiConn_getObjectType (conn, "OMNI_TRACER_PAYLOAD_ARRAY" , ..., & payloadType);
dpiConn_getObjectType (conn, "OMNI_TRACER_RAW_ARRAY" , ..., & rawType);
dpiConn_getObjectType (conn, "OMNI_TRACER_PAYLOAD_TYPE" , ..., & objType);
// Get attribute handle for JSON field
dpiObjectType_getAttributes (objType, 1 , & jsonAttr);
// Create variables for collections
dpiConn_newVar (conn, DPI_ORACLE_TYPE_OBJECT, ..., payloadType, & payloadVar, ...);
dpiConn_newVar (conn, DPI_ORACLE_TYPE_OBJECT, ..., rawType, & rawVar, ...);
// Execute PL/SQL dequeue procedure
ExecuteDequeueProc (conn, context, subscriberName, batchSize, waitTime,
payloadVar, rawVar, actualCount);
// Allocate result arrays
* outMessages = (TraceMessage * ) calloc ( * actualCount, sizeof (TraceMessage));
* outIds = (TraceId * ) calloc ( * actualCount, sizeof (TraceId));
// Extract payload messages
dpiObject_getFirstIndex (payloadColl, & idx, & exists);
while (exists) {
dpiObject_getElementValueByIndex (payloadColl, idx, ..., & element);
dpiObject_getAttributeValue (msgObj, jsonAttr, DPI_NATIVE_TYPE_LOB, & lobVal);
( * outMessages)[outIdx]. data = ReadLobContent ( lobVal . value . asLOB , ...);
// Next element
dpiObject_getNextIndex (payloadColl, idx, & idx, & exists);
}
// Extract raw IDs
dpiObject_getElementValueByIndex (rawColl, idx, ..., & rawElement);
memcpy (( * outIds)[outIdx]. data , rawElement . value . asBytes . ptr , len);
// Cleanup ODPI objects
cleanup:
dpiObjectType_release (payloadType);
dpiVar_release (payloadVar);
// ...
}
PL/SQL Procedure Execution
ExecuteDequeueProc function (lines 66-159) calls Oracle stored procedure:
const char * sql = "BEGIN OMNI_TRACER_API.Dequeue_Array_Events(:1, :2, :3, :4, :5, :6); END;" ;
Parameters:
:1 - Subscriber name (VARCHAR2)
:2 - Batch size (NUMBER)
:3 - Wait time (NUMBER)
:4 - Output payload array (OMNI_TRACER_PAYLOAD_ARRAY)
:5 - Output raw ID array (OMNI_TRACER_RAW_ARRAY)
:6 - Output count (NUMBER)
Error handling:
// ORA-25228: Timeout or end of fetch reached
if (errorInfo.code == 25228 ) {
* outCount = 0 ; // No messages available
result = 0 ; // Success (not an error)
goto cleanup;
}
LOB Content Reader
ReadLobContent function (lines 25-51) reads CLOB/BLOB data:
static char * ReadLobContent (dpiLob * lob , uint64_t * outLength ) {
// Get LOB size
dpiLob_getSize (lob, & size);
// Allocate buffer
char * buffer = ( char * ) malloc (size);
// Read LOB data
uint64_t bytesRead = size;
dpiLob_readBytes (lob, 1 , size, buffer, & bytesRead);
* outLength = bytesRead;
return buffer; // Caller must free()
}
The caller is responsible for freeing the returned buffer using FreeDequeueResults().
Memory Management
FreeDequeueResults function (lines 316-329) cleans up allocated memory:
void FreeDequeueResults (TraceMessage * messages , TraceId * ids , uint32_t count ) {
if (messages) {
for ( uint32_t i = 0 ; i < count; i ++ ) {
if ( messages [i]. data ) free ( messages [i]. data );
}
free (messages);
}
if (ids) {
for ( uint32_t i = 0 ; i < count; i ++ ) {
if ( ids [i]. data ) free ( ids [i]. data );
}
free (ids);
}
}
Always call FreeDequeueResults() after processing messages to avoid memory leaks. Each message and ID allocates heap memory that must be freed.
Using CGO in Go Code
While we don’t have the Go source files, here’s how CGO is typically used to call dequeue_ops.c:
package oracle
// #cgo CFLAGS: -I${SRCDIR}/../../../../third_party/odpi/include
// #cgo LDFLAGS: -L${SRCDIR}/../../../../third_party/odpi/lib -lodpi
// #include "dequeue_ops.h"
// #include <stdlib.h>
import " C "
import " unsafe "
func DequeueMessages ( conn * C . dpiConn , ctx * C . dpiContext , subscriber string , batchSize int , waitTime int ) ([] Message , error ) {
// Convert Go string to C string
cSubscriber := C . CString ( subscriber )
defer C . free ( unsafe . Pointer ( cSubscriber ))
var cMessages * C . TraceMessage
var cIds * C . TraceId
var actualCount C . uint32_t
// Call C function
result := C . DequeueManyAndExtract (
conn , ctx , cSubscriber ,
C . uint32_t ( batchSize ),
C . int32_t ( waitTime ),
& cMessages , & cIds , & actualCount ,
)
if result != 0 {
return nil , errors . New ( "dequeue failed" )
}
// Convert C results to Go
messages := make ([] Message , int ( actualCount ))
for i := 0 ; i < int ( actualCount ); i ++ {
msg := C . GetMessage ( cMessages , C . int ( i )) // Helper to get array element
messages [ i ] = Message {
Data : C . GoBytes ( unsafe . Pointer ( msg . data ), C . int ( msg . length )),
ID : // ... extract ID
}
}
// Free C memory
C . FreeDequeueResults ( cMessages , cIds , actualCount )
return messages , nil
}
Debugging CGO Issues
View CGO Compilation
See exactly how CGO compiles your C code:
This shows:
CGO compiler flags
CGO linker flags
GCC/Clang command lines
Which .c files are being compiled
[DEBUG] Checking CGO compilation...
CGO_CFLAGS=-I/Users/dev/omniview/third_party/odpi/include
CGO_LDFLAGS=-L/Users/dev/omniview/third_party/odpi/lib -lodpi -Wl,-rpath,/Users/dev/omniview/third_party/odpi/lib -Wl,-rpath,/opt/oracle/instantclient_23_7
Building oracle storage package with debug output:
cd /Users/dev/omniview/internal/adapter/storage/oracle
gcc -I. -fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments \
-fmessage-length=0 -fdebug-prefix-map=$WORK=/tmp/go-build \
-gno-record-gcc-switches -fno-common \
-I/Users/dev/omniview/third_party/odpi/include \
-o $WORK/b001/dequeue_ops.o -c dequeue_ops.c
Enable CGO Debug Mode
Get verbose CGO output during build:
CGO_ENABLED = 1 go build -x ./internal/adapter/storage/oracle
The -x flag prints all commands as they execute, including:
CGO preprocessing
C compilation
Object file linking
Common CGO Errors
undefined reference to 'dpiContext_create'
Problem : ODPI library not found or not linked properly.Solution :# Rebuild ODPI library
make clean
make odpi
# Verify library exists
ls -la third_party/odpi/lib/
# Check CGO flags
echo $CGO_LDFLAGS
dpi.h: No such file or directory
Problem : CGO_CFLAGS not set or incorrect include path.Solution :# Verify include directory exists
ls third_party/odpi/include/dpi.h
# Set CGO_CFLAGS manually
export CGO_CFLAGS = "-I$( pwd )/third_party/odpi/include"
make build
library not found for -lodpi (macOS)
Problem : Linker can’t find libodpi.dylib.Solution :# Check library exists
ls third_party/odpi/lib/libodpi.dylib
# Rebuild if missing
make odpi
# Set library path
export DYLD_LIBRARY_PATH = $( pwd )/ third_party / odpi / lib : $DYLD_LIBRARY_PATH
The specified module could not be found (Windows)
Problem : odpi.dll or Oracle DLLs not in PATH.Solution :# Copy DLL to workspace root
copy third_party\odpi\lib\odpi.dll .
# Add Oracle Instant Client to PATH
set PATH =% PATH % ;C:\oracle_inst\instantclient_23_7
Troubleshooting CGO Issues
Runtime Library Loading
Ensure shared libraries are found at runtime:
# Check what libraries the binary needs
otool -L ./omniview
# Output should show:
# @rpath/libodpi.dylib
# /opt/oracle/instantclient_23_7/libclntsh.dylib
# Set runtime search path
export DYLD_LIBRARY_PATH = $( pwd )/ third_party / odpi / lib :/ opt / oracle / instantclient_23_7
./omniview
# Check library dependencies
ldd ./omniview
# Set library path
export LD_LIBRARY_PATH = $( pwd )/ third_party / odpi / lib :/ opt / oracle / instantclient_23_7
./omniview
# Check dependencies (using Dependency Walker or similar)
# Or just run and check error message
# Add to PATH
set PATH =% PATH % ; % CD % \third_party\odpi\lib;C:\oracle_inst\instantclient_23_7
omniview.exe
CGO Memory Issues
CGO has important memory rules:
C code allocates memory with malloc() - must free() in C
Go code passes pointers to C - must not move during C call
Use C.CString() for Go→C strings (remember to free!)
Use C.GoString() or C.GoBytes() for C→Go data
Common memory mistakes:
// ❌ WRONG: String gets freed before C function returns
name := "subscriber"
result := C . DoSomething ( C . CString ( name ))
// ✅ CORRECT: Keep pointer alive and free after
cName := C . CString ( name )
defer C . free ( unsafe . Pointer ( cName ))
result := C . DoSomething ( cName )
// ❌ WRONG: Go pointer might move during C call
var data [] byte
C . ProcessData ( unsafe . Pointer ( & data [ 0 ])) // Dangerous!
// ✅ CORRECT: Pin memory or copy to C-allocated buffer
cData := C . malloc ( C . size_t ( len ( data )))
defer C . free ( cData )
C . memcpy ( cData , unsafe . Pointer ( & data [ 0 ]), C . size_t ( len ( data )))
Oracle Connection Issues
If dequeue operations fail:
// Enable ODPI-C error reporting in dequeue_ops.c
#define CHECK_DPI ( status , ctx , msg ) \
if (status != DPI_SUCCESS) { \
dpiErrorInfo errorInfo; \
dpiContext_getError ((ctx), & errorInfo); \
fprintf (stderr, "[C ERROR] %s : %.*s \n " , \
(msg), ( int ) errorInfo . messageLength , errorInfo . message ); \
return - 1 ; \
}
All ODPI errors are printed to stderr with full Oracle error messages.
Environment Variables
Required for Building
# Oracle Instant Client location
export INSTANT_CLIENT_DIR = / opt / oracle / instantclient_23_7
# CGO must be enabled (default is 1)
export CGO_ENABLED = 1
Optional for Development
# Force CGO to use specific compiler
export CC = gcc-11
# Add extra compiler flags
export CGO_CFLAGS = " $CGO_CFLAGS -DDEBUG -g"
# Add extra linker flags
export CGO_LDFLAGS = " $CGO_LDFLAGS -v"
Why Use CGO?
CGO adds complexity but provides benefits for OmniView:
Performance Direct C calls to Oracle are 2-5x faster than database/sql for bulk operations
Advanced Features ODPI-C supports Oracle-specific features like AQ, object types, and collections
Memory Efficiency Streaming LOB data without intermediate buffers
Batch Operations Dequeue multiple messages in single call
CGO Overhead
CGO calls have ~10-50ns overhead per call:
Good : Batch operations (dequeue 100 messages)
Bad : Per-row processing (call C for each row)
OmniView uses CGO efficiently by batching dequeue operations.
Best Practices
Always Free C Memory
Every malloc() in C code must have corresponding free(). Use defer in Go.
Check Error Returns
All ODPI-C functions return status codes. Always check them.
Use defer for Cleanup
Release ODPI objects in reverse order of acquisition using defer.
Handle NULL Values
Check isNull flag before accessing ODPI data values.
Test with CGO_ENABLED=1
Some bugs only appear when CGO is actually used.
Next Steps
Building from Source Complete guide to building OmniView
Makefile Reference All available make targets explained