콘텐츠로 이동

(KO) CUBRID loadjava — JavaSP 클래스로더 트리를 위한 JAR · .class 설치기

저장 프로시저 코드 설치기 는 사용자 코드를 개발자가 작성한 곳 (로컬 파일 시스템의 빌드 artefact)에서 엔진이 찾을 수 있는 곳 (런타임이 watch 하는 정규 install 경로)으로 옮기는 utility 이다. 저장 프로시저용 자기 런타임을 ship 하는 엔진은 하나씩 필요 하다 — Postgres 는 CREATE FUNCTION ... LANGUAGE plpython3u AS $$ ... $$ (외부 설치기 없음; 코드가 카탈로그에 산다); Oracle 은 loadjava (binary copy + 카탈로그 등록); DB2 는 db2 INSTALL JAR; MongoDB 는 db.system.js.save().

CUBRID 가 JavaSP 사용자 코드를 호스트하는 JVM 사이드카 (cub_plcubrid-pl-javasp.md 참조)를 ship 한다. 사이드카의 클래스로더 hierarchy 가 사용자 JAR 을 per-database 디렉토리에 root 한다는 점이다. loadjava 는 그 디렉토리에 .class 또는 .jar 를 올바른 package 아래 올바른 path 로 떨궈주는 파일시스템-측 도구이다.

의도된 디자인 선택은 파일시스템-측 설치 + mtime 을 통한 JVM 발견 이다. 설치기는 데이터베이스 서버에 절대 연결하지 않는다. 파일을 그저 복사한다는 뜻이다. JVM 의 classloader manager (pl_engine/pl_server/)가 디렉토리의 modification time 을 주기적 으로 검사하고 변경되면 reload 한다 — 새로운 loadjava 가 JVM 재시작 없이 다음 SP invocation 에 pickup 되게 한다. copy 직전의 비범한 fs::remove (단지 copy_options::overwrite_existing 에 의존하지 않고)가, 새 파일의 metadata 가 우연히 옛 것과 매치 할 때조차 부모 디렉토리의 mtime 이 진행되도록 보장하는 부분이다.

각 등록된 데이터베이스를 JVM 이 두 디렉토리 트리를 watch 한다는 점이다:

트리경로Loader목적
Dynamic$CUBRID_DATABASES/<db>/java/<package>/<file>ContextClassLoader (per-context, mtime polling 으로 변경 pickup)기본 install 위치; 여기 JAR 은 cub_pl 재시작 없이 추가/업데이트 가능
Static$CUBRID_DATABASES/<db>/java_static/<package>/<file>ServerClassLoader (cub_pl 시작 시 한 번 로드)권한 있는 trusted classpath 에 있어야 하는 코드 install 위치; 업데이트가 cub_pl 재시작 필요

loadjava 는 dynamic 트리를 default 한다. --jni (-j)가 static 트리를 선택한다. static 트리는 java_static 으로 이름지어지며 specifically JNI 통해 native 라이브러리를 호출해야 하는 클래스 용이다 — 그것들이 더 높은 클래스로더와 함께 오는 security-manager 허용이 필요하다는 뜻이다.

loadjava [--overwrite] [--package <pkg>] [--jni] <db_name> <src.class|src.jar>
├─ parse_argument:
│ -y / --overwrite → Force_overwrite = true
│ -p <pkg> / --package → /^[a-z_][a-z0-9_]*(\.[a-z_][a-z0-9_]*)*$/ 에 대해 검증
│ 그 다음 dot → SEPARATOR (예: "org.example.foo" → "org/example/foo")
│ -j / --jni → Path = "java_static" (그 외에는 "java")
├─ check_arguments:
│ cfg_find_db (Dbname) → $CUBRID_DATABASES/<db> 해석; Root 설정
│ fs::exists (Src_class) → 소스 파일 존재해야
│ extension 검사 → .class 또는 .jar 여야
└─ do_load_java:
class_file_name = src.filename ()
check_overwrite (package_path, class_file_name)
├─ dest 가 java/ 또는 java_static/ 어디에든 존재하면:
│ !Force_overwrite 면: "(y/n)" prompt; 'y' 가 아니면 bail
│ fs::remove (existing) — 부모 mtime update
└─ create_package_directories (Root / Path / package_path)
0744 perms 로 fs::create_directories
copy_class_file (Src_class, dest_path)
overwrite_existing 와 fs::copy
// loadjava.cpp:53
static const std::string JAVA_PACKAGE_PATTERN =
"^([a-z_]{1}[a-z0-9_]*(\\.[a-z_]{1}[a-z0-9_]*)*)$";

regex 가 conventional Java package 모양을 강제한다는 점이다:

  • 각 segment 가 lowercase 글자 또는 _ 로 시작한다.
  • 후속 글자는 lowercase, 숫자, 또는 _.
  • segment 가 . 으로 분리.
  • match 가 case-insensitive (icase) 라서 Foo.Bar 는 uppercase 포함으로 거절되지만 foo.bar 는 받아들여진다 — regex 가 case 별 로 동작을 변경하지는 않지만 검증이 의도적으로 lowercase 컨벤션 에 엄격하다는 뜻이다.

Package-name 위반은 invalid java package name 을 출력하고 bail 한다. 빈 package (--package 없음)는 유효하다 — 설치가 unpackaged root (<dbpath>/java/<file>)로 간다.

JVM 이 파일 mtime 을 watch 한다면 conventional fs::copy with overwrite_existing 만으로 충분할 것이다. 하지만 classloader- manager (pl_engine/pl_server/ 안)가 per-파일 mtime 이 아니라 디렉토리 mtime 을 poll 한다 — 디렉토리 mtime 이 파일이 추가 또는 제거될 때 안정적으로 진행되는 것이다. 그래서 loadjava 가 명시적으로 destination 을 먼저 삭제한다는 점이다:

// check_overwrite — loadjava.cpp
if (exists_static && fs::is_directory (static_path) == false) {
fs::remove (static_path);
}
if (exists_dynamic && fs::is_directory (dynamic_path) == false) {
fs::remove (dynamic_path);
}

그 다음 이어지는 fs::copy 가 이제 비어 있는 slot 에 fresh 파일 을 land 한다. 두 번의 파일-시스템 동작 (delete + create)이 바이트가 변경되지 않을 때조차 디렉토리 mtime 이 진행되도록 보장한다는 뜻이다.

delete 옆 CBRD-24695 코멘트가 이를 specific 버그 픽스에 pin 한다 — 이전에는 같은-크기, 같은-mtime 파일로 교체된 JAR 가 classloader reload 를 trigger 하지 않았다.

check_overwrite 가 install target 만이 아니라 static 과 dynamic 둘 다 트리를 검사한다. 즉 dynamic 트리에 install 하면 static 트리의 중복도 감지한다 (그리고 prompt 또는 제거):

fs::path static_path = Root / STATIC_PATH / package_path / class_file_name;
fs::path dynamic_path = Root / DYNAMIC_PATH / package_path / class_file_name;
bool exists_static = fs::exists (static_path);
bool exists_dynamic = fs::exists (dynamic_path);
if (exists_static || exists_dynamic) {
if (!Force_overwrite) prompt; bail-on-no;
if (exists_static) fs::remove (static_path);
if (exists_dynamic) fs::remove (dynamic_path);
}

Cross-tree 제거가 stale 클래스로더가 새 install 을 가리는 것을 방지한다 — 같은 Foo.class 가 둘 다에 존재하면, static loader (JVM 시작 시 먼저 로드)가 이긴다. 그래서 static 을 제거하지 않고 fresh dynamic copy 를 install 하면 효과가 없다는 뜻이다.

loadjava 는 파일시스템-측만 이다. db_restart 하지 않고, cub_mastercub_pl 과 말하지 않고, auth 자격증명이 필요 없다. 설치는 데이터베이스의 디렉토리 아래 파일 복사일 뿐이다.

이는 디자인이다:

  • 설치는 데이터베이스가 offline 일 때조차 작동해야 한다.
  • JVM 의 classloader-manager 가 mtime polling 으로 변경을 pickup 한다 — notification 프로토콜 필요 없음.
  • 설치된 JAR 을 참조하는 JavaSP 카탈로그 행 (_db_stored_procedure_codecubrid-pl-javasp.md §Catalog rows 참조)은 loadjava 가 아니라 CREATE PROCEDURE / CREATE FUNCTION 문으로 csql 로 쓰여진다.

split (파일시스템 install 은 loadjava 통해, 카탈로그 등록은 DDL 통해)이 운영자가 해당 CREATE PROCEDURE 가 발사되기 전에 JAR 을 디스크에 stage 할 수 있게 (또는 DDL 을 다시 발사하지 않고 이미 등록된 procedure 의 JAR 을 update 할 수 있게) 해주는 부분이다.

Terminal window
# package 없이 demodb dynamic 트리에 foo.jar install
loadjava demodb /tmp/foo.jar
# com.example 아래 demodb dynamic 트리에 Bar.class install
loadjava --package com.example demodb /tmp/Bar.class
# prompt 없이 강제 install Baz.jar; static (JNI 허용) 트리
loadjava -y --jni demodb /tmp/Baz.jar

파일이 land 하는 곳:

  • $CUBRID_DATABASES/demodb/java/foo.jar
  • $CUBRID_DATABASES/demodb/java/com/example/Bar.class
  • $CUBRID_DATABASES/demodb/java_static/Baz.jar

저장 프로시저가 다음에 이 JAR 의 어떤 클래스를 참조하면, classloader- manager 가 디렉토리의 mtime 변경을 알아채고 reload 한다는 뜻이다.

심볼역할
main진입; 네 단계 (utility init, parse, check, do_load_java) orchestration
parse_argument-y -p <pkg> -j -h 의 getopt_long; Force_overwrite, package_path, Path, Dbname, Src_class 채움
check_argumentscfg_find_db 통해 db 경로 해석; 소스 파일 존재와 .class 또는 .jar extension 검사
do_load_javacheck_overwritecreate_package_directoriescopy_class_file 호출
check_overwritecross-tree 존재 검사; non--y 에 prompt; 디렉토리 mtime bump 위한 명시적 fs::remove
create_package_directories0744 perms 로 fs::create_directories
copy_class_fileoverwrite_existingfs::copy
usage메시지 카탈로그 usage 텍스트 출력
JAVA_PACKAGE_PATTERN (regex)Package-name 검증 패턴
JAVA_DIR / JAVA_STATIC_DIR (define)트리 이름
심볼경로
mainsrc/executables/loadjava.cpp:325
parse_argumentsrc/executables/loadjava.cpp:76
check_argumentssrc/executables/loadjava.cpp:166
do_load_javasrc/executables/loadjava.cpp:295
check_overwritesrc/executables/loadjava.cpp:229
JAVA_PACKAGE_PATTERNsrc/executables/loadjava.cpp:53

심볼 이름이 정규 anchor 이고, 라인 번호는 updated: 날짜에 스코프된 힌트이다.

  • Standalone 바이너리, cubrid verb 가 아니다. loadjava 는 이름 패턴으로만 util_front.c 의 레거시-shim 표에 있다 (실제로 는 거기 없다). 운영자는 직접 invoke 한다. ua_Utility_Map 에도 없다. 빌드 target 이 자기 바이너리이다.
  • -j / --jni 가 오해를 부르는 이름이다. java_static 트리 를 선택한다 — native 라이브러리를 로드하도록 허용된 클래스가 사는 곳이라 JNI 트리 라고도 불린다. 플래그가 JNI-specific 한 뭔가를 하는 건 아니다 — 단지 install 경로를 바꾼다는 점이다.
  • 카탈로그 write 없음. _db_stored_procedure* 행에 SP 등록 은 CREATE PROCEDURE 의 일이지 loadjava 의 일이 아니다. 흔한 운영자 실수가 loadjava 를 돌리고 SP 가 callable 할 것이라 기대하는 것이다 — CREATE PROCEDURE Foo (...) AS LANGUAGE JAVA NAME 'Foo.bar' 가 두 번째 단계라는 뜻이다.
  • fs::copy 전의 fs::remove 가 의도적이다. copy 전에 기존 파일을 제거하는 것이 부모 디렉토리의 mtime 이 진행되게 만드는 부분이다. loadjava 대신 cp foo.jar $CUBRID_DATABASES/demodb/ java/ 로 short-cut 하는 운영자는 파일을 install 하지만 다른 무언가가 디렉토리를 건드릴 때까지 classloader reload 를 trigger 하지 않는다.
  • C++17 <filesystem>. loadjava.cpp 는 C++17 stdlib 을 쓰는 src/executables/ 의 몇 안 되는 파일 중 하나이다. 더 오래된 toolchain 을 target 하는 빌드 시스템은 feature 플래그가 필요할 수 있다 — executables 패밀리의 나머지는 대체로 C99 / C++11.
  • Multi-file install 없음. loadjava 가 invocation 당 정확히 한 파일을 install 한다. N 파일을 install 하려면 N 번 돌린다. --directory 또는 wildcard 모드가 없다.
  • 제거 counterpart. unloadjava 가 존재하지 않는다. install 된 JAR 제거는 install 트리에서 rm 이 필요하다 (그리고 classloader 가 mtime 통해 pickup 할 것). 카탈로그 reference 를 cross- check 하는 first-class unloadjava 가 더 안전할 것이다.
  • Package-name case sensitivity. regex 가 case-insensitive 이지 만 Java 자체는 package 이름을 case-sensitive 이다. 디렉토리 를 실수로 Foo 로, 클래스를 foo.Bar 로 이름지은 사용자는 설치기가 잡지 못하는 mismatch 를 가진다는 뜻이다.
  • 권한. 설치는 0744 이며 group/other 읽기 가능하다. cub_plloadjava 를 돌리는 사용자와 다른 사용자로 도는 환경에서는 group 읽기 가능성이 install 을 접근 가능하게 만드는 부분이다. 더 빡빡한 setup (다른 group, group 읽기 안 됨)은 post-install chgrp 가 필요할 것이다.
  • src/executables/loadjava.cpp — 전체 utility (단일 파일, 356 줄)
  • src/executables/AGENTS.md — agent 가이드
  • 인접 문서: cubrid-pl-javasp.md (JavaSP 런타임; loadjava 가 쓰는 디렉토리를 watch 하는 classloader-manager), cubrid-pl-plcsql.md (다른 PL 패밀리 멤버; PL/CSQL 은 컴파일된 bytecode 를 디스크가 아니라 카탈로그에 저장하므로 loadjava 를 쓰지 않는다), cubrid-pl-server-bridge.md (두 런타임이 사용 하는 콜백 채널), cubrid-cub-admin.md (통합 admin CLI; loadjava 는 없다)