콘텐츠로 이동

(KO) CUBRID csql — 인터랙티브 SQL 클라이언트, 두 바이너리 launcher 분리, 세션 명령 prefix, 단일행 실행 모드

인터랙티브 SQL 클라이언트는 데이터베이스 운영자가 프롬프트에서 SQL 을 입력하고 결과가 흘러내려가는 것을 보면서, 세션 상태 (미커밋 트랜잭션, 환경 설정, prepared statement) 를 점진적으로 쌓아가는 데 쓰이는 프로그램이다. 모든 관계형 엔진이 하나씩 함께 배포한다. 디자인 표면적은 작지만 그 디자인 선택이 엔진의 연결 모델과 런타임 철학을 잘 드러낸다.

세 가지 축이 디자인 공간을 지배한다.

  1. 연결 모델 축이다. 클라이언트가 동작 중인 서버를 네트워크로 호출하거나 (PostgreSQL psql → libpq → TCP/UDS, MySQL mysql → 클라이언트 라이브러리 → TCP/UDS, Oracle sqlplus → OCI → SQL*Net), 엔진을 자기 프로세스에 직접 링크한다 (SQLite shell, Berkeley DB shell). CUBRID 는 같은 클라이언트 바이너리로 두 모델을 모두 지원한다. launch time 의 설정 스위치 (--SA-mode vs --CS-mode) 가 어느 엔진 라이브러리 를 로드할지를, 그래서 클라이언트 프로세스 자체가 데이터 베이스 엔진 (standalone) 인지 네트워크 peer (client-server) 인지를 결정한다.

  2. 문장-완결 감지 축이다. SQL 은 사용자가 (또는 구현이 강제할 때까지) 고정된 문장 종결자를 가지지 않는다. PostgreSQL psql; 를 요구하고 libpq 의 토크나이저로 버퍼가 string, comment, dollar-quoted block 안에서 끝났는지 감지 한다. MySQL mysql; (또는 \g/\G) 와 비슷한 in-block tracker 를 쓰고, Oracle sqlplus; 또는 자기 라인의 / 로 PL/SQL block 을 보낸다. CUBRID 는 두 모드를 모두 노출한다. 하나는 사용자가 명시적 ;run / ;xrun 으로 버퍼를 commit 할 때까지 입력을 받는 버퍼 editor 모델이고, 다른 하나는 string / comment / identifier block 어디에도 안 걸리고 깨끗하게 lex 되자마자 각 완결 문장을 자동으로 submit 하는 단일행 실행 모드다.

  3. Out-of-band 명령 축이다. 모든 인터랙티브 클라이언트는 SQL 해석에서 클라이언트-로컬 메타 명령 해석으로 전환하는 명령 prefix 문자를 예약한다. psqlmysql\, sqlplus@ / & / :, SQLite 구버전의 :, SQLite 신버전의 . 가 그 예다. CUBRID 는 ; 를 쓴다. 보통 SQL 이 문장 종결자로 쓰는 그 문자다. 첫 글자 위치만으로 모호성을 해소 하기 때문에, 입력된 줄이 ; 로 시작하면 세션 명령이고 아니면 SQL 이다.

csql 클라이언트는 모던 클라이언트 대부분이 떨궈낸 네 번째 관심사 도 함께 들고 있다. --sysadm 으로 호출되었을 때 시스템 관리자의 shell 역할도 맡는다. 매일 사용자가 연결하는 같은 바이너리를 DBA 가 카탈로그 재구축이나 standby 복제본 위에 쓰기 같은 일을 하는 데도 쓴다. 이 경로는 argv 플래그와 사용자가 정말 DBA 인지에 대한 단단한 검사로 게이트된다.

클라이언트링키지문장 경계메타 명령 prefix주목할 특성
PostgreSQL psqllibpq, 네트워크 전용; + in-block 추적\ (\d, \timing, \copy)readline / libedit, pset 으로 출력 포맷팅
MySQL mysql클라이언트 라이브러리, 네트워크 전용; 또는 \g / \G시스템 명령에 \, ; 가 문장을 끝냄--batch 로 비-인터랙티브 출력, --raw 로 escaping 억제
Oracle sqlplusOCI, 네트워크 전용; 또는 / (PL/SQL)@, &, :, SET, SHOWSET 패밀리가 30+ 개의 출력 / 동작 노브를 제어
SQL Server sqlcmdODBC / TDS, 네트워크 전용자기 라인의 GO:!, :r, :setvar배치는 ; 가 아니라 GO 에서 끝남
SQLite shell임베디드 SQLite 라이브러리; + in-block 추적. (.tables, .mode, .import)같은 바이너리가 엔진을 임베드, 클라이언트 / 서버 선택 없음
CUBRID csql--SA-mode / --CS-mode 에 따른 cubridsa 또는 cubridcs 의 dlopen;run / ;xrun (버퍼) 또는 자동 감지 (단일행 모드); (세션 명령 prefix)dual SA/CS launcher, 같은 바이너리가 임베디드 엔진 또는 네트워크 클라이언트

CUBRID 의 두 차별점 (dual SA/CS launcher, SQL 종결자와 같은 문자를 공유하는 세션 명령 prefix) 은 둘 다 OODB 유산에서 내려 왔다. DBA 사용자에게 엔진을 돌리는 경험과 엔진을 사용하는 경험의 경계를 의도적으로 흐려 두던 시절이다.

사용자는 단일 바이너리 (csql) 를 호출하지만, 두 개의 object 파일이 관여한다. csql_launcher.c 가 실행 파일의 진입점이고, 실제 작업은 두 공유 라이브러리 (cubridsa for standalone, cubridcs for client-server) 중 하나에 컴파일된 csql.c 안에 들어 있다. launcher 의 일은 argv 파싱과 라이브러리 선택이며, 실제 csql() 함수는 요청된 모드에 맞는 라이브러리에서 dlsym 으로 가져온다.

// main — csql_launcher.c
GETOPT_LONG csql_option[] = {
{CSQL_SA_MODE_L, 0, 0, CSQL_SA_MODE_S},
{CSQL_CS_MODE_L, 0, 0, CSQL_CS_MODE_S},
{CSQL_USER_L, 1, 0, CSQL_USER_S},
{CSQL_PASSWORD_L, 1, 0, CSQL_PASSWORD_S},
/* ... 27 total options ... */
{VERSION_L, 0, 0, VERSION_S},
{0, 0, 0, 0}
};
/* ... while (getopt_long(...)) populates csql_arg ... */
if (csql_arg.sa_mode) {
utility_load_library (&util_library, LIB_UTIL_SA_NAME); // libcubridsa
} else {
utility_load_library (&util_library, LIB_UTIL_CS_NAME); // libcubridcs
}
utility_load_symbol (util_library, (DSO_HANDLE *) (&csql), "csql");
error = (*csql) (argv[0], &csql_arg);

이 분리는 두 가지 결과를 낳는다.

  • 모드 선택은 launch 시점에 최종 확정된다. launcher 가 cubridsacubridcs 를 해석해 csql 을 가져온 순간부터, 세션 전체가 그 엔진 라이브러리를 쓴다. 세션 안에서 다른 모드로 전환하는 길은 없고, 재연결을 하려면 바이너리를 재시작해야 한다 (;connect 세션 명령은 다른 데이터베이스를 target 으로 할 수 는 있지만 SA↔CS 를 전환하지는 못한다).
  • csql() 심볼이 install 에 함께 배포된다. 같은 C 소스 (csql.c) 가 다른 conditional define (SA_MODE vs CS_MODE) 으로 libcubridsalibcubridcs 양쪽에 컴파일된다. 함수 본체는 거의 동일하고, 차이는 #if defined(SA_MODE) 블록으로 guard 된다 (예: connect / disconnect 경로, sysadm 모드의 auth bypass, locator skip-vacuum 토글).

launcher 의 getopt_long switch 가 평탄한 CSQL_ARGUMENT 구조체 (csql.h 에 선언)를 채우고, 파싱 루프 다음에 일련의 검증 규칙이 어떤 라이브러리도 로드되기 전에 잘못된 조합을 거절한다는 점이다:

// csql_launcher.c — 검증 규칙 (의역)
if (csql_arg.plain_output) check_output_style++;
if (csql_arg.query_output) check_output_style++;
if (csql_arg.loaddb_output) check_output_style++;
if (check_output_style > 1) goto print_usage; // 상호 배타
if (csql_arg.sysadm_rebuild_catalog) {
if (!csql_arg.sa_mode) goto print_usage; // rebuild 는 SA 필요
if (in_file == NULL && command == NULL)
goto print_usage; // 그리고 -i 또는 -c
}
if ((csql_arg.sysadm || csql_arg.sysadm_rebuild_catalog)
&& (csql_arg.user_name == NULL ||
strcasecmp (csql_arg.user_name, "DBA")))
goto print_usage; // sysadm = DBA 만
if (csql_arg.sysadm == false && csql_arg.write_on_standby == true)
goto print_usage; // --write-on-standby 는 --sysadm 필요
if (csql_arg.sa_mode &&
(csql_arg.cs_mode || csql_arg.write_on_standby))
goto print_usage; // SA 와 CS-only 플래그 혼용 불가
if (!csql_arg.sa_mode && csql_arg.skip_vacuum)
goto print_usage; // --skip-vacuum 은 SA 전용
if (explicit_single_line && csql_arg.single_line_execution == false)
goto print_usage; // -s 와 --no-single-line 충돌

검증은 의도적으로 방어적이다 — 모든 잘못된 조합이 db_restart_ex() 가 돌기 전에 감지되어, 사용자 실수로 데이터 베이스 연결 시도가 발생하지 않는다는 뜻이다.

launcher 가 설정하는 CSQL_ARGUMENT 기본값은 정책 기록이다:

// csql_launcher.c — 기본값
csql_arg.auto_commit = true; // 문장당 commit
csql_arg.single_line_execution = true; // 첫 완결 문장에서 submit
csql_arg.string_width = 0; // 컬럼 truncation 무제한
csql_arg.trigger_action_flag = true; // 트리거 정상 실행
csql_arg.column_delimiter = -1; // unset; -q 도 unset 이면 ',' 로 설정
csql_arg.column_enclosure = -1; // unset; -q 도 unset 이면 '\'' 로 설정
csql_arg.midxkey_print = false; // 숨겨진 ;midxkey 토글

launcher 가 심볼을 해석하고 나면, csql() 함수 (진입점은 csql.c:3161)가 나머지를 구동한다:

// csql — csql.c
csql_exit_init (); // 심각 에러용 setjmp env
if (setjmp (csql_Exit_env)) { // ... 어디서나 longjmp 가능
csql_exit_cleanup ();
logddl_destroy ();
return csql_Exit_status;
}
utility_initialize (); // 메시지 카탈로그
/* prompt 설정: "csql> " 또는 "csql_sysadm> " */
if (csql_arg->in_file_name != NULL)
csql_Input_fp = fopen (csql_arg->in_file_name, "r");
if (csql_arg->in_file_name == NULL && isatty (fileno (stdin)))
csql_Is_interactive = true;
er_init ("./csql.err", ER_NEVER_EXIT); // 에러 로그 파일
/* SIGABRT/SIGILL/SIGFPE/SIGBUS/SIGSEGV/SIGSYS 등록 */
/* sysadm / read_only / skip_vacuum 플래그에서 client_type 계산 */
client_type = DB_CLIENT_TYPE_CSQL;
if (csql_arg->sysadm) client_type = DB_CLIENT_TYPE_ADMIN_CSQL;
if (csql_arg->write_on_standby) client_type = DB_CLIENT_TYPE_ADMIN_CSQL_WOS;
if (csql_arg->skip_vacuum) client_type = DB_CLIENT_TYPE_SKIP_VACUUM_ADMIN_CSQL;
if (csql_arg->read_only) client_type = DB_CLIENT_TYPE_READ_ONLY_CSQL;
if (db_restart_ex (argv0, db_name, user_name, passwd, NULL, client_type) != NO_ERROR) {
/* 인터랙티브이고 패스워드가 안 주어졌으면 prompt 후 한 번 재시도 */
p = getpass (csql_get_message (CSQL_PASSWD_PROMPT_TEXT));
/* ... db_restart_ex 재시도 ... */
}
logddl_init (APP_NAME_CSQL); // DDL audit
logddl_check_ddl_audit_param ();
db_set_client_ip_addr (ip_addr);
/* -A 인 경우: db_disable_trigger() */
/* --sysadm 이고 DBA 인 경우: AU_DISABLE(save) — authorisation 우회 */
start_csql (csql_arg); // read 루프

client_type 이 중요하다는 점이다 — 서버 측 세션 표 (cubrid-server-session.md 참조)에 기록되며, HA 복제, locator vacuum, lock manager 가 이 연결이 standby 에 쓸 수 있는지, vacuum 을 건너뛸 수 있는지 등을 판단하는 데 쓴다는 뜻이다. 같은 클라이언트 바이너리가 launch flag 에 따라 다른 “내가 누구인가” identity 를 광고하는 것이다.

setjmp/longjmp exit 환경은 글로벌 escape hatch 이다 — csql 안 어디서나 csql_exit (status) 를 부르면 top 으로 longjmp 하고 cleanup 을 거친다. 모든 단계가 에러 반환을 main 까지 thread 할 필요가 없게 해준다는 점이다.

인터랙티브 루프는 start_csql (csql.c:665)에 산다는 점이다. 세 가지 입력 경로가 여기로 합쳐진다:

  1. -c "stmt" 경로. CLI 에서 전달된 한 문장이 csql_execute_statements (..., STRING_INPUT, command, -1) 로 한 번 실행되고, 이어서 csql_exit_session 으로 종료한다.
  2. -i file 비-단일행 모드. 파일 전체가 한 번에 csql_execute_statements (..., FILE_INPUT, csql_Input_fp, -1) 로 먹여진다.
  3. 인터랙티브 또는 단일행 모드. 메인 for (line_no = 1; ; line_no++) 루프가 한 번에 한 줄씩 읽는다.

인터랙티브 분기는 Unix 에서 GNU readline() 을 쓰고 (stifle_history (PRM_ID_CSQL_HISTORY_NUM) 으로 히스토리 한도 설정), Windows 에서는 fgets() 를 쓴다. 각 줄이 그 다음 디스 패치된다는 뜻이다:

// start_csql — csql.c (의역)
for (line_no = 1; ; line_no++) {
prompt = (db_Connect_status == DB_CONNECTION_STATUS_CONNECTED)
? csql_Prompt : csql_Prompt_offline; // "csql> " vs "!csql> "
line_read = readline (prompt); // or fgets()
/* '\n' 과 '\r' 제거 */
if (CSQL_SESSION_COMMAND_PREFIX (line_read[0]) // 첫 글자가 ';'
&& (csql_Is_interactive || !is_in_block)) {
ret = csql_do_session_cmd (line_read, csql_arg); // ;-cmd 디스패치
if (ret == DO_CMD_EXIT) csql_exit_session (0, true);
if (ret == DO_CMD_FAILURE) goto error_continue;
continue;
}
csql_edit_contents_append (line_read, read_whole_line); // 누적
if (feof (csql_Input_fp)) {
csql_execute = true; // end-of-input: flush
is_in_block = false;
} else {
csql_walk_statement (line_read); // 이 줄을 lex
is_in_block = csql_is_statement_in_block (); // walker 가 갱신
if (csql_arg->single_line_execution
&& read_whole_line == true
&& csql_is_statement_complete ()) {
csql_execute = true; // 단일행 submit
}
}
if (csql_execute) {
csql_execute_statements (csql_arg, EDITOR_INPUT, NULL, line_no);
csql_edit_contents_clear ();
}
}

csql_walk_statement 는 라인 경계를 가로질러 점진적으로 도는 stateful lexer 이다 — 버퍼가 현재 미종결 string, 미종결 /* ... */ comment, 미종결 identifier ("...") 안에 있는지 flag 를 갱신한다. is_in_block 검사가 ;포함하는 다중행 string literal 을 안전하게 입력할 수 있게 해주는 부분이다 — 단일행 실행은 string 이 닫힐 때까지 기다린다는 뜻이다.

Prompt 전환 (csql_Promptcsql_Prompt_offline 의 교체) 은 연결이 끊겼다는 가시적 신호다. db_Connect_statusDB_CONNECTION_STATUS_NOT_CONNECTED 를 보고하면 (보통 서버 크래시나 ;restart 후) csql>!csql> 로 바뀐다.

;-prefixed 명령들은 csql_session.c 의 단일 표에서 정의된다는 점이다:

csql_session.c
static SESSION_CMD_TABLE csql_Session_cmd_table[] = {
/* File stuffs */
{"read", S_CMD_READ, CMD_EMPTY_FLAG},
{"write", S_CMD_WRITE, CMD_EMPTY_FLAG},
{"append", S_CMD_APPEND, CMD_EMPTY_FLAG},
{"print", S_CMD_PRINT, CMD_EMPTY_FLAG},
{"shell", "!", S_CMD_SHELL, CMD_EMPTY_FLAG}, // !; 별칭
{"cd", S_CMD_CD, CMD_EMPTY_FLAG},
{"exit", S_CMD_EXIT, CMD_EMPTY_FLAG},
/* Edit stuffs */
{"clear", S_CMD_CLEAR, CMD_EMPTY_FLAG},
{"edit", S_CMD_EDIT, CMD_EMPTY_FLAG},
{"list", S_CMD_LIST, CMD_EMPTY_FLAG},
/* Command stuffs */
{"run", S_CMD_RUN, CMD_CHECK_CONNECT},
{"xrun", S_CMD_XRUN, CMD_CHECK_CONNECT},
{"commit", S_CMD_COMMIT, CMD_CHECK_CONNECT},
{"rollback", S_CMD_ROLLBACK, CMD_CHECK_CONNECT},
{"autocommit", S_CMD_AUTOCOMMIT, CMD_EMPTY_FLAG},
{"checkpoint", S_CMD_CHECKPOINT, CMD_CHECK_CONNECT},
{"killtran", S_CMD_KILLTRAN, CMD_CHECK_CONNECT},
{"restart", S_CMD_RESTART, CMD_EMPTY_FLAG},
/* Environment stuffs */
{"shell_cmd", S_CMD_SHELL_CMD, CMD_EMPTY_FLAG},
{"editor_cmd", S_CMD_EDIT_CMD, CMD_EMPTY_FLAG},
{"print_cmd", S_CMD_PRINT_CMD, CMD_EMPTY_FLAG},
{"pager_cmd", S_CMD_PAGER_CMD, CMD_EMPTY_FLAG},
{"nopager", S_CMD_NOPAGER_CMD, CMD_EMPTY_FLAG},
{"formatter_cmd", S_CMD_FORMATTER_CMD,CMD_EMPTY_FLAG},
{"column-width", S_CMD_COLUMN_WIDTH, CMD_EMPTY_FLAG},
{"string-width", S_CMD_STRING_WIDTH, CMD_EMPTY_FLAG},
{"set", S_CMD_SET_PARAM, CMD_CHECK_CONNECT},
{"get", S_CMD_GET_PARAM, CMD_CHECK_CONNECT},
{"plan", S_CMD_PLAN_DUMP, CMD_CHECK_CONNECT},
{"echo", S_CMD_ECHO, CMD_EMPTY_FLAG},
{"date", S_CMD_DATE, CMD_EMPTY_FLAG},
{"time", S_CMD_TIME, CMD_EMPTY_FLAG},
{"line-output", S_CMD_LINE_OUTPUT, CMD_EMPTY_FLAG},
{".hist", S_CMD_HISTO, CMD_EMPTY_FLAG},
{".clear_hist", S_CMD_CLR_HISTO, CMD_EMPTY_FLAG},
{".dump_hist", S_CMD_DUMP_HISTO, CMD_EMPTY_FLAG},
{".x_hist", S_CMD_DUMP_CLR_HISTO, CMD_EMPTY_FLAG},
/* Help stuffs */
{"help", S_CMD_HELP, CMD_EMPTY_FLAG},
{"schema", S_CMD_SCHEMA, CMD_CHECK_CONNECT},
{"database", S_CMD_DATABASE, CMD_CHECK_CONNECT},
{"trigger", S_CMD_TRIGGER, CMD_CHECK_CONNECT},
{"info", S_CMD_INFO, CMD_EMPTY_FLAG},
/* History stuffs */
{"historyread", S_CMD_HISTORY_READ, CMD_EMPTY_FLAG},
{"historylist", S_CMD_HISTORY_LIST, CMD_EMPTY_FLAG},
{"trace", S_CMD_TRACE, CMD_CHECK_CONNECT},
{"singleline", S_CMD_SINGLELINE, CMD_EMPTY_FLAG},
{"connect", S_CMD_CONNECT, CMD_EMPTY_FLAG},
{"midxkey", S_CMD_MIDXKEY, CMD_EMPTY_FLAG}, // 숨김, ;help 에 없음
{"server-output", S_CMD_SERVER_OUTPUT,CMD_CHECK_CONNECT}
};

이 표가 dot-command 표면 전체에 해당한다. 일곱 패밀리에 걸친 47 항목이다. 끌어낼 만한 디자인 노트 몇 가지를 적어 둔다.

  • Prefix matching, longest-exact-wins. csql_get_session_cmd_no 가 표를 걸어가며 case-insensitive prefix match 를 센다. 정확히 하나의 항목이 매칭되면 그게 이기고, 여러 항목이 매칭되면 CSQL_ERR_SESS_CMD_AMBIGUOUS 다. 즉 ;ru;run 이고 (단일 매치), ;c 는 모호하다 (clear, commit, checkpoint, cd, connect, column-width).
  • 빈 tail 특수 케이스. prefix 뒤에 이름이 없는 빈 ;S_CMD_XRUN 으로 매핑된다 (버퍼를 실행하고 비우라는 뜻). 가장 흔한 동작이라 한 키 입력으로 최적화되어 있다.
  • !;shell 별칭이다. prefix 뒤의 단일 문자 ! 가 shell 을 invoke 한다. ;!;shell 이 동등하다.
  • CMD_CHECK_CONNECT 플래그. 이 비트가 표시된 명령은 (CS_MODE 에서만) db_Connect_status != DB_CONNECTION_STATUS_CONNECTED 일 때 거절된다. 이 플래그가 모든 DDL-class 명령(run, commit, schema, set/get param, plan, trace, server-output)과 트랜잭션 명령(commit, rollback, checkpoint, killtran)을 덮는다. DDL 인접 하거나 순수 로컬 명령(cd, edit, autocommit, history)은 CMD_EMPTY_FLAG 로 표시되어 연결 상태와 무관하게 동작한다는 뜻이다.
  • .hist / .clear_hist / .dump_hist / .x_hist 가 outlier 다. 이름에 leading dot 이 있어 (;.hist 로 invoke 됨) — 레거시 PERFMON 스타일 histogram namespace 와 충돌하기 때문 이다. 튜닝 실행을 위한 호출당 통계를 dump 한다.

csql_help_menu() (csql_session.c:206)는 메시지 카탈로그에서 도움말 텍스트를 렌더링하면서 midxkey 는 의도적으로 빠뜨린다 — 숨김 플래그, 내부 전용이다.

csql_walk_statement (csql_support.c 안)는 start_csql 에서 줄마다 호출되는 stateful lexer 다. 호출들 너머로 상태를 유지한다는 점이다:

  • 현재 위치가 string literal ('...', "...", 또는 backtick) 안에 있는지 — 그렇다면 무엇이 닫는지.
  • 현재 위치가 comment (-- ..., /* ... */) 안에 있는지.
  • 현재 위치가 [ ] (Microsoft 스타일) 또는 backtick (MySQL 스타일) 로 둘러싸인 identifier 안에 있는지.

csql_is_statement_in_block() 은 이들 중 어느 하나가 미종결이면 true 를 돌려주고, csql_is_statement_complete() 는 이들 중 어느 것도 열려 있지 않고 그리고 버퍼가 어떤 block 밖의 ; 로 끝났을 때 true 를 돌려준다는 뜻이다.

이것이 다중행 SQL 에서 단일행 실행을 안전하게 만들어주는 부분 이다 — '...' 안에 ; 들이 있는 CREATE PROCEDURE 본문은 일찍 submit 되지 않는다. closing quote 가 올 때까지 string-block 플래그가 켜져 있기 때문이다.

네 가지 상호 배타적 출력 스타일은 launcher 의 check_output_style 검증과 짝을 이룬다는 점이다:

스타일플래그형식
기본 (column)(없음)헤더 바와 정렬된 컬럼, --string-width 에서 truncation, 사람이 읽는 모드
Line-output--line-output / -l행마다 한 라인에 한 컬럼 (column = value), 매우 넓은 행에 유용
Plain-p헤더 없음, separator 없음, 컬럼이 공백으로 분리, shell pipeline 용
Query-qCSV 스타일, column-delimiter (기본 ,) 와 column-enclosure (기본 ') 설정 가능
Loaddb--loaddb-output'-enclosed, 공백 분리, loaddb 가 다시 import 할 수 있는 파일 생성

launcher 가 세션이 인터랙티브일 때 (-c 도 없고 -i 도 없을 때) plain, query, loaddb 스타일을 비활성화하고 플래그와 무관하게 기본 column 출력으로 되돌린다 — 그 스타일들은 batch / pipeline 사용에서만 의미가 있기 때문이다.

형식 자체는 csql_result_format.c (~1980 줄)에 산다. 헤더 파이프라인은:

db_Query_result → csql_results() (csql_result.c)
→ 각 행마다: csql_db_value_as_string() (csql_result_format.c)
→ DB_TYPE 별 formatter (string, number, date, set, OID, ...)

csql_db_value_as_string 이 universal DB_VALUE → text 함수다. 같은 코드 경로가 네 출력 스타일 모두에 쓰이며, formatter 파라미터(delimiter, enclosure, width)가 달라진다는 점이다.

--sysadm 이 csql 을 권한 있는 shell 로 만든다. launcher 는 이를 DBA (사용자 이름 case-insensitive)로 제한한다. 엔진 진입은 추가 변경을 적용한다는 뜻이다:

  • Client type 이 DB_CLIENT_TYPE_ADMIN_CSQL 이 된다 (또는 --write-on-standby / --skip-vacuum 도 켜져 있으면 더 특화된 admin 변형 중 하나).
  • 연결 사용자가 정말 DBA 그룹에 있을 때만 (au_is_dba_group_member (Au_user)) AU_DISABLE (save) 가 호출되어 authorisation 을 완전히 우회한다.
  • Prompt 가 csql_sysadm> 로 바뀌어 운영자가 세션이 권한 있다는 것을 시각적으로 안다는 점이다.
  • --sysadm-rebuild-catalog 는 별도의 더 좁은 변형이다 — SA 모드를 강제하고 -i 또는 -c 를 요구한다 (인터랙티브 사용 없음). cross-version 마이그레이션 동안 쓰는 카탈로그 재구성 모드.

--write-on-standby--sysadm 이 필요하고 HA standby 데이터 베이스에서 상태를 변형할 수 있는 유일한 방법이다 — DR 훈련과 긴급 개입에 쓴다.

히스토리는 Unix 에서 GNU readline 으로 backed 된다 (start_csql#if !defined(WINDOWS) 블록이 stifle_history (PRM_ID_CSQL_HISTORY_NUM)using_history() 를 부른다). 두 가지 평행한 “히스토리” 개념이 공존한다는 점이다:

  1. Readline command-line 히스토리. 입력된 줄을 위 화살표로 recall 한다. 사용자에게 readline config 가 있을 때만 세션 너머로 persist 된다.
  2. Statement-execution histogram. ;.hist 가 문장당 성능 카운터 (lock wait, page access, query plan 전환) 캡처를 활성화하고, ;.dump_hist 가 출력하고, ;.clear_hist 가 reset 하며, ;.x_hist 는 dump-then-clear 다. 엔진의 PERFMON 시설 (cubrid-monitoring.md) 위에 얹혀 있고 현재 세션에 scoped 된다.

둘은 독립적이다 — readline 히스토리는 순수 클라이언트 로컬 이고, .hist 는 같은 세션으로 연결된 엔진에서 카운터를 읽는 다는 뜻이다.

logddl_init (APP_NAME_CSQL)logddl_check_ddl_audit_param ()csql() 안에서 일찍 호출된다. ddl_audit_log 시스템 파라미터가 활성화되어 있으면, 이 csql 세션으로 발사된 모든 DDL 문이 timestamp, 사용자 ID, 클라이언트 IP, 원래 SQL 텍스트와 함께 per-database audit 파일로 로깅된다. DDL audit 서브시스템 은 csql 전용이 아니다 — broker / CCI 도 logddl_* 를 부른다 — 하지만 csql 이 admin DDL 의 흔한 시작점이라 보통 audit 로그의 가장 큰 contributor 가 된다.

get_host_ip()db_set_client_ip_addr() 가 audit 로그가 기록 하는 IP 를 채운다. 같은 주소가 서버 측 세션 표에도 저장되어 SHOW THREADS / SHOW TRANS 가 표시한다.

심볼역할
main진입점; getopt 루프로 CSQL_ARGUMENT 채움; 검증 사슬; 엔진 라이브러리 dlopen/dlsym; 해석된 csql() 호출
utility_csql_usagecubridsa 를 로드하고 csql_get_messagedlsym; 메시지 카탈로그에서 usage 렌더링
utility_csql_print메시지 카탈로그의 비-치명 launcher 메시지를 위한 가변인자 printer
csql_option[]27 항목의 GETOPT_LONG
심볼역할
csql라이브러리 진입; setjmp exit env 설정, 입력 파일 열기, 시그널 핸들러 등록, client_type 계산, db_restart_ex 호출, 패스워드 재시도, start_csql 호출
csql_exit_init치명-exit 단축을 위한 setjmp longjmp env 설치
csql_exit / csql_exit_session / csql_exit_cleanup계층화된 exit 경로; session 형식은 auto_commit 에 따라 commit 또는 rollback
start_csqlRead-execute-print 루프; prompt, 단일행 vs 버퍼 모드, in-block 추적 관리
csql_walk_statement (in csql_support.c)Stateful per-line lexer; string / comment / identifier block 추적
csql_is_statement_in_block / csql_is_statement_completeLexer 상태에 대한 술어
csql_edit_contents_append / _clear / _finalizesubmission 사이의 in-memory editor 버퍼 관리
csql_execute_statementssubmission entrypoint; STRING_INPUT / FILE_INPUT / EDITOR_INPUT 으로 디스패치
csql_print_buffer;list / ;edit / ;print 지원 — 현재 editor 버퍼 렌더
csql_print_database;database 지원 — 데이터베이스 이름과 connect 상태의 형식화 표시
csql_display_trace;trace 지원 — 가장 최근 실행된 쿼리의 XASL trace
change_prompt;set csql_prompt 지원 — 사용자 정의 형식을 사용자 이름으로 치환

세션 명령 디스패치 (csql_session.c)

섹션 제목: “세션 명령 디스패치 (csql_session.c)”
심볼역할
csql_Session_cmd_table[]47 항목의 ;-명령 표
csql_get_session_cmd_noSESSION_CMD enum 값을 돌려주는 prefix-match resolver
csql_help_menu메시지 카탈로그에서 ;help 렌더링
csql_do_session_cmd (in csql.c)Top-level dispatcher; SESSION_CMD switch 로 명령별 핸들러 호출
csql_killtran;killtran 지원 — 트랜잭션 list 와 선택적 abort
csql_dump_alltranlisting 을 위한 ;killtran formatter
csql_pipe_handlerpager 가 세션을 죽이지 않게 하기 위한 SIGPIPE 핸들러

결과 렌더링 (csql_result.c, csql_result_format.c)

섹션 제목: “결과 렌더링 (csql_result.c, csql_result_format.c)”
심볼역할
csql_resultsTop-level 결과 iterator; 헤더, pagination, count 처리
csql_db_value_as_string (in csql_result_format.c)universal DB_VALUE → text formatter; width, delimiter, enclosure 존중
Per-DB_TYPE formattersper-type 렌더링: numeric, datetime, string, set, sequence, multiset, JSON, OID, BLOB / CLOB locator
csql_pipe_to_pager (in csql_support.c)설정된 pager (PAGER_CMD)를 fork 하고 출력을 pipe
심볼역할
csql_walk_statementStateful lexer (start_csql 에서 줄마다 호출)
csql_pipe_to_pager / csql_pipe_handlerPager 통합
csql_get_message / csql_get_help_message메시지 카탈로그 accessor
csql_check_server_downread 루프 동안 주기적 probe — 서버 크래시 감지 후 offline prompt 로 전환
심볼경로
main (launcher)src/executables/csql_launcher.c:114
csql_option[]src/executables/csql_launcher.c:124
CSQL_ARGUMENT (struct)src/executables/csql.h:305
csql (엔진 진입)src/executables/csql.c:3161
start_csqlsrc/executables/csql.c:665
csql_exit_initsrc/executables/csql.c:3032
csql_print_buffersrc/executables/csql.c:1927
csql_print_databasesrc/executables/csql.c:2592
csql_display_tracesrc/executables/csql.c:3723
display_buffersrc/executables/csql.c:438
csql_Session_cmd_table[]src/executables/csql_session.c:75
csql_get_session_cmd_nosrc/executables/csql_session.c:150
csql_help_menusrc/executables/csql_session.c:206
SESSION_CMD (enum, S_CMD_*)src/executables/csql.h:185

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

  • Launcher 와 엔진 검증이 중복된다. 일부 플래그 조합은 두 번 검사된다. 라이브러리가 로드되기 전에 launcher 에서 한 번, 엔진 진입 (csql()) 에서 한 번이다. 중복은 의도적이다. launcher 검사가 호출을 일찍 그리고 싸게 거절해 dlopen 비용을 안 내게 하고, 엔진 검사는 caller 가 csql 심볼을 직접 invoke 하는 드문 경로 (예: 커스텀 임베디드 도구) 를 방어한다.
  • CMD_CHECK_CONNECT#if defined (CS_MODE) 전용이다. SA 모드에는 disconnected 상태가 없다. 엔진이 in-process 라서 연결 검사가 컴파일에서 빠진다. SA-모드 csql 에서 발사된 ;commit 은 항상 엔진에 도달하고, 엔진 자신이 commit 할 tran 이 있는지 결정한다.
  • db_Connect_status 는 글로벌이다. 엔진 라이브러리 (db_restart_ex, db_shutdown) 가 변형하고 prompt 전환을 위해 루프가 읽는다. csql 이 단일 스레드이기 때문에 작동한다. csql 이 background thread 를 들이는 순간 (예: async query progress) 메모리 barrier 또는 mutex 가 필요하다.
  • csql() 심볼은 한 프로세스 안에서 런타임에 선택할 수 없다. dlopen 된 라이브러리는 launcher invocation 의 lifetime 동안 영구적이라, 모드 전환은 fresh csql 을 exec() 해야 한다.
  • 숨겨진 ;midxkey 는 표에는 있지만 ;help 렌더링에서 제외 된다. 내부 multi-index-key trace 출력을 토글하는 엔진 개발자 용이고 end user 용이 아니다.
  • stifle_history 는 readline 측이다. 엔진 측 query 히스토리가 아니다. 둘은 독립적이고, ;historyread / ;historylist 는 shell 측 명령 히스토리 (readline 이 유지하는 것) 가 아니라 editor 버퍼 히스토리에 동작한다.
  • tty_fpstart_csql 에서 csql_Error_fp 로 설정되는 것은 인터랙티브일 때뿐이다. batch (-i 또는 piped stdin) 에서는 NULL 이고, 이 값이 prompt 와 conversational 메시지를 억제 한다. start_csql 의 doc-comment 가 정규 reference 다.
  • Linux 의 readline 대체. 빌드는 현재 GNU readline 에 링크한다. 일부 배포는 라이선스 이유로 libedit 를 선호한다. csql 이 양쪽 에 portable 해질 수 있는지는 (API 가 거의 호환된다) 문서화되지 않았다.
  • 다중 문장 단일행 실행. csql_is_statement_complete 는 라인 안에 더 있어도 첫 완결 문장에서 true 를 돌려준다. SELECT 1; SELECT 2; 같이 입력된 라인의 동작은 첫째를 실행하고 나머지를 버리는 형태다 (csql_execute_statements 후의 csql_edit_contents_clear 가 버퍼를 떨군다). 이것이 의도된 설계인지, 라인 안의 모든 완결 문장 실행으로 다듬을 만한 버그 인지는 미정이다.
  • query_output 스타일과 pager 의 상호작용. csql_pipe_to_pager 의 pager forwarding 은 terminal 출력을 무조건적으로 한다. -q 출력을 pager 로 pipe 하면 이중 형식화가 생긴다. 운영자는 보통 -q--no-pager 를 같이 쓰지만, launcher 가 강제 하지는 않는다.
  • 세션 명령 도움말 국제화. csql_help_menu 는 메시지 카탈로그 에서 도움말 텍스트를 읽지만, 표 자체 (csql_Session_cmd_table[]) 는 영어 이름 prefix 를 hard-code 한다. 한국어 / 일본어 ;-명령 을 원하는 locale 은 평행 표가 필요한데 존재하지 않는다.
  • src/executables/csql_launcher.c — argv 파서, 검증, 라이브러리 로더
  • src/executables/csql.c — 엔진 진입, 메인 루프, 세션 명령 디스패처, 버퍼 관리, exit 처리
  • src/executables/csql.hCSQL_ARGUMENT 구조체, SESSION_CMD enum, 메시지 상수, 함수 prototype
  • src/executables/csql_session.c — 세션 명령 표와 prefix-match resolver, ;help 렌더링, killtran formatting
  • src/executables/csql_result.c — 결과 iterator 와 pagination
  • src/executables/csql_result_format.cDB_VALUE text formatter, per-type 렌더링, 출력 스타일 적용
  • src/executables/csql_support.c — stateful per-line lexer (csql_walk_statement), pager 통합, 메시지 카탈로그 도우미, 서버-down probe
  • src/executables/AGENTS.md — csql 책임과 빌드-target 매핑을 명명한 agent 가이드 (CS-mode 바이너리는 cs/CMakeLists.txt)
  • 인접 문서: cubrid-coverage.md (utilities batch 안에서의 위치), cubrid-server-session.md (csql client_type 을 기록하는 서버 측 세션 표), cubrid-monitoring.md (.hist 뒤의 PERFMON 시설), cubrid-broker.md (broker 프로세스; CS-mode csql 은 broker 로 cub_server 에 닿는다), cubrid-cdc.md / cubrid-ha-replication.md (--write-on-standby 가 의미를 갖는 컨텍스트)