Skip to content

CUBRID Procedural Language — Section Overview

This section describes CUBRID’s procedural language family — the two stored-procedure surfaces a user can write against. The first is JavaSP: a Java stored procedure where the user supplies a pre-compiled .class or .jar and the engine dispatches into it by reflection. The second is PL/CSQL: an Oracle-dialect procedural SQL whose source text is parsed, type-checked, lowered to Java, compiled with javac in memory, and stored as bytecode in the catalog. Despite the very different user-facing syntax, the two share almost the entire runtime. Both are persisted in the same three catalog system classes, both ride the same Unix-domain-socket wire protocol from cub_server to a sidecar cub_pl JVM process, and both are dispatched by the same C++ executor and the same Java ExecuteThread. Only the final step that turns the user’s input into something callable differs. Read this overview first to understand the shape of the family; then read the two detail docs to see how each frontend plugs in.

flowchart TB
    subgraph cub_server["cub_server (C/C++ process)"]
        catalog["Catalog rows<br/>_db_stored_procedure*<br/>(sp_catalog.cpp)"]
        compile_h["pl_compile_handler.cpp<br/>(PL/CSQL DDL only)"]
        executor["pl_executor.cpp<br/>session, invoke_java"]
        connpool["pl_connection.cpp<br/>connection_pool"]
    end

    subgraph wire["Wire — UDS or TCP"]
        proto["RequestCode-tagged<br/>binary frames<br/>(pl_comm.h)"]
    end

    subgraph cub_pl["cub_pl (one external JVM)"]
        listener["ListenerThread.java<br/>(accept loop)"]
        execthread["ExecuteThread.java<br/>(per connection)"]
        subgraph javasp_fe["JavaSP frontend"]
            target["TargetMethod.java<br/>reflective dispatch"]
            cl["ClassLoaderManager<br/>(user JARs)"]
            sec["SpSecurityManager"]
        end
        subgraph plcsql_fe["PL/CSQL frontend"]
            antlr["ANTLR<br/>PlcLexer/PlcParser.g4"]
            ast["AST<br/>Decl* / Stmt* / Expr*"]
            tc["TypeChecker"]
            emit["JavaCodeWriter<br/>(emit Java source)"]
            javac["MemoryJavaCompiler<br/>(javax.tools)"]
        end
    end

    catalog --- executor
    compile_h --> connpool
    executor --> connpool
    connpool --> proto
    proto --> listener
    listener --> execthread
    execthread -->|INVOKE_SP JavaSP| target
    target --> cl
    target --> sec
    execthread -->|COMPILE PL/CSQL| antlr
    antlr --> ast --> tc --> emit --> javac
    javac -. bytecode .-> catalog
    execthread -->|INVOKE_SP PL/CSQL| target

    classDef shared fill:#e8f0ff,stroke:#3060a0;
    class catalog,executor,connpool,proto,listener,execthread shared;

The diagram shows the three layers of the family. The blue-shaded nodes are shared between both frontends; everything else is language-specific.

Shared C-side glue (src/sp/). The catalog half lives in sp_catalog.cpp / sp_catalog.hpp, which writes the sp_info, sp_arg_info, and sp_code_info structs into the system classes _db_stored_procedure, _db_stored_procedure_args, and _db_stored_procedure_code. Attribute names are centralised in sp_constants.hpp. The transport half lives in pl_connection.cpp and pl_comm.c: a pooled set of Unix-domain-socket (or TCP) connections to the sidecar JVM, wrapped in RAII connection_view handles. The dispatch half lives in pl_executor.cpp and pl_session.cpp: every stored-procedure call — JavaSP or PL/CSQL — is packaged into an invoke_java message by the C++ executor class, and the per-CUBRID-session execution stack is held by session::create_and_push_stack. The executor does not care which language produced the procedure; the catalog row tells it the target method name and the wire protocol carries the args.

Shared JVM (pl_engine/pl_server). A single cub_pl process is forked at server boot by pl_start_jvm_server() (or, in the modern external-process model, spawned by server_manager::start() via create_child_process("cub_pl", db_name)). Inside the JVM, Server.java installs the security manager and binds the listening socket; ListenerThread.java accepts incoming connections and spawns one ExecuteThread.java per active link. ExecuteThread decodes RequestCode headers and routes to either reflective dispatch (INVOKE_SP for JavaSP), the in-process compiler (COMPILE for PL/CSQL DDL), or the same reflective dispatch path again (INVOKE_SP for PL/CSQL — once compiled it is indistinguishable from a hand-written JavaSP).

Two frontends. JavaSP (see cubrid-pl-javasp.md) brings its own classloader hierarchy under pl_engine/pl_server/.../classloader/, a SpSecurityManager that blocks System.exit() and native-library loading from user code, and TargetMethod.java to resolve ClassName.methodName(argTypes) by reflection. PL/CSQL (see cubrid-pl-plcsql.md) brings ANTLR 4 grammars (PlcLexer.g4, PlcParser.g4, StaticSqlWithRecords.g4), the compiler/ast/ tree of Decl* / Stmt* / Expr* nodes, a TypeChecker that calls back to the C server for static-SQL semantics, a JavaCodeWriter that walks the checked AST and emits a Java source string, and MemoryJavaCompiler which wraps javax.tools.JavaCompiler to produce bytecode in memory. The C-side coordinator for that compile-on-DDL round trip is pl_compile_handler.cpp, which is the only piece of src/sp/ that PL/CSQL adds.

Read cubrid-pl-javasp.md first. It introduces the shared infrastructure — process topology, the cub_pl boot sequence, the SP_CODE / RequestCode wire protocol, the connection pool, the pl_session execution stack, the catalog rows — in detail, because JavaSP exercises every layer except the compiler. Once you have read JavaSP you understand the entire substrate.

Then read cubrid-pl-plcsql.md. It builds on top of the substrate and only describes what is added: the ANTLR front end, the AST and type system, the Java emitter, the in-process javac, and the pl_compile_handler round-trip. PL/CSQL deliberately delegates execution to the JavaSP path — the PL/CSQL compiler’s output is, by construction, just another Java class that lives in the catalog. So a reader who already understands JavaSP can focus entirely on the compiler in the PL/CSQL doc, with no need to re-read shared sections.

Reading them in the opposite order works but forces the PL/CSQL doc to forward-reference shared infrastructure that is more naturally introduced under JavaSP.

A handful of design choices are worth naming explicitly because they apply to both frontends and are easy to miss when you read the detail docs in isolation.

  • One catalog, no language tag at the storage level. Both JavaSP and PL/CSQL persist as rows in _db_stored_procedure, _db_stored_procedure_args, and _db_stored_procedure_code. The sp_info struct in sp_catalog.hpp does not branch on language; the JVM-side dispatcher distinguishes a JavaSP from a PL/CSQL procedure only by where the bytecode came from (user-supplied JAR versus emitted by the in-process compiler). This means catalog utilities — schema dumps, loaddb, replication of DDL — handle both kinds uniformly.

  • One wire, one socket. Both rides of the family use the same pooled Unix-domain-socket (or TCP fallback) link from cub_server to cub_pl, the same RequestCode-tagged frames built by CUBRIDPacker / CUBRIDUnpacker, and the same connection_pool / connection_view lifetime model. There is no separate “PL/CSQL channel”; the only difference between calls is the request code in the header.

  • Process isolation is the safety story. Because cub_pl is a separate OS process — not an in-process JNI embedding — a user Java class that crashes the JVM, runs out of permgen, or stalls in GC cannot take down the SQL server. A monitor task inside cub_server (server_monitor_task) reaps and respawns cub_pl on death. This isolation applies equally to PL/CSQL because PL/CSQL ultimately runs as Java inside the same cub_pl.

  • No language-aware fast path. The executor does not have a shortcut for PL/CSQL nor a shortcut for JavaSP. Every call pays the IPC and reflection cost. The trade-off — accepted explicitly during the migration off the legacy JNI embedding documented in pl_sr_jvm.cpp — is reliability over peak call rate.

DocScopeWhat is unique to it
cubrid-pl-javasp.mdJava stored procedures end-to-end. Owns the shared-infrastructure exposition: cub_pl process topology, boot via pl_start_jvm_server / server_manager::start, SP_CODE and METHOD_REQUEST wire protocol, connection_pool / connection_view, pl_session execution stack, server-side JDBC callback channel.Reflective dispatch (TargetMethod.java), classloader hierarchy (ClassLoaderManager, ContextClassLoader, SessionClassLoader, ServerClassLoader), security sandbox (SpSecurityManager), legacy in-process JNI path (pl_sr_jvm.cpp) preserved as historical context.
cubrid-pl-plcsql.mdThe Oracle-dialect compile-on-DDL pipeline. Builds on JavaSP’s runtime; everything below the AST is borrowed.ANTLR 4 grammars (PlcLexer.g4, PlcParser.g4, StaticSqlWithRecords.g4), AST (Decl*, Stmt*, Expr*, Unit), TypeChecker with ServerAPI callback for embedded static SQL, type system (Type, TypeChar, TypeNumeric, TypeRecord, TypeVariadic, Coercion, CoercionScheme), JavaCodeWriter emitter, MemoryJavaCompiler, and the C-side pl_compile_handler.cpp round-trip.

The PL family does not stand alone; the following sections supply context that the detail docs assume you already know.

  • DDL & Schema. cubrid-ddl-execution.md and the catalog notes explain how CREATE PROCEDURE reaches the catalog manager in the first place — including transactional semantics for catalog inserts. The PL/CSQL compile-on-DDL hook plugs into that path; the PL family doc takes the catalog rows as given.

  • Server architecture. cubrid-architecture-overview.md explains the SA-vs-CS runtime split (standalone vs client-server modes) and where cub_pl sits relative to cub_server. cubrid-network-protocol.md explains the client-facing wire protocol; the PL family runs a separate, internal protocol on a different socket, but reuses the same CUBRIDPacker framing primitives.

  • Query processing. PL CALL statements enter through the same XASL execution path as ordinary SQL — a CALL node in the execution plan invokes pl_executor.cpp::invoke_java. So everything the query-processing section says about XASL, predicate evaluation, and result fetching applies before and after a PL invocation; the PL family doc only covers what happens inside the call.

When in doubt, read this overview, then JavaSP, then PL/CSQL — and treat DDL, server architecture, and query processing as the surrounding chapters that the PL family deliberately offloads work to.