(KO) CUBRID csql — 인터랙티브 SQL 클라이언트, 두 바이너리 launcher 분리, 세션 명령 prefix, 단일행 실행 모드
이론적 배경
섹션 제목: “이론적 배경”인터랙티브 SQL 클라이언트는 데이터베이스 운영자가 프롬프트에서 SQL 을 입력하고 결과가 흘러내려가는 것을 보면서, 세션 상태 (미커밋 트랜잭션, 환경 설정, prepared statement) 를 점진적으로 쌓아가는 데 쓰이는 프로그램이다. 모든 관계형 엔진이 하나씩 함께 배포한다. 디자인 표면적은 작지만 그 디자인 선택이 엔진의 연결 모델과 런타임 철학을 잘 드러낸다.
세 가지 축이 디자인 공간을 지배한다.
-
연결 모델 축이다. 클라이언트가 동작 중인 서버를 네트워크로 호출하거나 (PostgreSQL
psql→ libpq → TCP/UDS, MySQLmysql→ 클라이언트 라이브러리 → TCP/UDS, Oraclesqlplus→ OCI → SQL*Net), 엔진을 자기 프로세스에 직접 링크한다 (SQLite shell, Berkeley DB shell). CUBRID 는 같은 클라이언트 바이너리로 두 모델을 모두 지원한다. launch time 의 설정 스위치 (--SA-modevs--CS-mode) 가 어느 엔진 라이브러리 를 로드할지를, 그래서 클라이언트 프로세스 자체가 데이터 베이스 엔진 (standalone) 인지 네트워크 peer (client-server) 인지를 결정한다. -
문장-완결 감지 축이다. SQL 은 사용자가 (또는 구현이 강제할 때까지) 고정된 문장 종결자를 가지지 않는다. PostgreSQL
psql은;를 요구하고 libpq 의 토크나이저로 버퍼가 string, comment, dollar-quoted block 안에서 끝났는지 감지 한다. MySQLmysql은;(또는\g/\G) 와 비슷한 in-block tracker 를 쓰고, Oraclesqlplus는;또는 자기 라인의/로 PL/SQL block 을 보낸다. CUBRID 는 두 모드를 모두 노출한다. 하나는 사용자가 명시적;run/;xrun으로 버퍼를 commit 할 때까지 입력을 받는 버퍼 editor 모델이고, 다른 하나는 string / comment / identifier block 어디에도 안 걸리고 깨끗하게 lex 되자마자 각 완결 문장을 자동으로 submit 하는 단일행 실행 모드다. -
Out-of-band 명령 축이다. 모든 인터랙티브 클라이언트는 SQL 해석에서 클라이언트-로컬 메타 명령 해석으로 전환하는 명령 prefix 문자를 예약한다.
psql과mysql의\,sqlplus의@/&/:, SQLite 구버전의:, SQLite 신버전의.가 그 예다. CUBRID 는;를 쓴다. 보통 SQL 이 문장 종결자로 쓰는 그 문자다. 첫 글자 위치만으로 모호성을 해소 하기 때문에, 입력된 줄이;로 시작하면 세션 명령이고 아니면 SQL 이다.
csql 클라이언트는 모던 클라이언트 대부분이 떨궈낸 네 번째 관심사
도 함께 들고 있다. --sysadm 으로 호출되었을 때 시스템 관리자의
shell 역할도 맡는다. 매일 사용자가 연결하는 같은 바이너리를
DBA 가 카탈로그 재구축이나 standby 복제본 위에 쓰기 같은 일을
하는 데도 쓴다. 이 경로는 argv 플래그와 사용자가 정말 DBA
인지에 대한 단단한 검사로 게이트된다.
일반 DBMS 디자인
섹션 제목: “일반 DBMS 디자인”| 클라이언트 | 링키지 | 문장 경계 | 메타 명령 prefix | 주목할 특성 |
|---|---|---|---|---|
| PostgreSQL psql | libpq, 네트워크 전용 | ; + in-block 추적 | \ (\d, \timing, \copy) | readline / libedit, pset 으로 출력 포맷팅 |
| MySQL mysql | 클라이언트 라이브러리, 네트워크 전용 | ; 또는 \g / \G | 시스템 명령에 \, ; 가 문장을 끝냄 | --batch 로 비-인터랙티브 출력, --raw 로 escaping 억제 |
| Oracle sqlplus | OCI, 네트워크 전용 | ; 또는 / (PL/SQL) | @, &, :, SET, SHOW | SET 패밀리가 30+ 개의 출력 / 동작 노브를 제어 |
| SQL Server sqlcmd | ODBC / 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 사용자에게 엔진을 돌리는 경험과 엔진을 사용하는 경험의 경계를 의도적으로 흐려 두던 시절이다.
CUBRID 의 접근
섹션 제목: “CUBRID 의 접근”두 바이너리 launcher 패턴
섹션 제목: “두 바이너리 launcher 패턴”사용자는 단일 바이너리 (csql) 를 호출하지만, 두 개의 object
파일이 관여한다. csql_launcher.c 가 실행 파일의 진입점이고,
실제 작업은 두 공유 라이브러리 (cubridsa for standalone,
cubridcs for client-server) 중 하나에 컴파일된 csql.c 안에
들어 있다. launcher 의 일은 argv 파싱과 라이브러리 선택이며,
실제 csql() 함수는 요청된 모드에 맞는 라이브러리에서
dlsym 으로 가져온다.
// main — csql_launcher.cGETOPT_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 가
cubridsa나cubridcs를 해석해csql을 가져온 순간부터, 세션 전체가 그 엔진 라이브러리를 쓴다. 세션 안에서 다른 모드로 전환하는 길은 없고, 재연결을 하려면 바이너리를 재시작해야 한다 (;connect세션 명령은 다른 데이터베이스를 target 으로 할 수 는 있지만 SA↔CS 를 전환하지는 못한다). - 두
csql()심볼이 install 에 함께 배포된다. 같은 C 소스 (csql.c) 가 다른 conditional define (SA_MODEvsCS_MODE) 으로libcubridsa와libcubridcs양쪽에 컴파일된다. 함수 본체는 거의 동일하고, 차이는#if defined(SA_MODE)블록으로 guard 된다 (예: connect / disconnect 경로, sysadm 모드의 auth bypass, locator skip-vacuum 토글).
CLI 옵션 디스패치와 검증
섹션 제목: “CLI 옵션 디스패치와 검증”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; // 문장당 commitcsql_arg.single_line_execution = true; // 첫 완결 문장에서 submitcsql_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 토글엔진 진입: csql() 함수
섹션 제목: “엔진 진입: csql() 함수”launcher 가 심볼을 해석하고 나면, csql() 함수 (진입점은
csql.c:3161)가 나머지를 구동한다:
// csql — csql.ccsql_exit_init (); // 심각 에러용 setjmp envif (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 auditlogddl_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 할
필요가 없게 해준다는 점이다.
Read-execute-print 루프 (start_csql)
섹션 제목: “Read-execute-print 루프 (start_csql)”인터랙티브 루프는 start_csql (csql.c:665)에 산다는 점이다.
세 가지 입력 경로가 여기로 합쳐진다:
-c "stmt"경로. CLI 에서 전달된 한 문장이csql_execute_statements (..., STRING_INPUT, command, -1)로 한 번 실행되고, 이어서csql_exit_session으로 종료한다.-i file비-단일행 모드. 파일 전체가 한 번에csql_execute_statements (..., FILE_INPUT, csql_Input_fp, -1)로 먹여진다.- 인터랙티브 또는 단일행 모드. 메인
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_Prompt 와 csql_Prompt_offline 의 교체) 은
연결이 끊겼다는 가시적 신호다. db_Connect_status 가
DB_CONNECTION_STATUS_NOT_CONNECTED 를 보고하면 (보통 서버
크래시나 ;restart 후) csql> 가 !csql> 로 바뀐다.
세션 명령 디스패치 표
섹션 제목: “세션 명령 디스패치 표”;-prefixed 명령들은 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 | -q | CSV 스타일, 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 모드
섹션 제목: “Sysadm 모드”--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() 를 부른다). 두 가지 평행한 “히스토리” 개념이
공존한다는 점이다:
- Readline command-line 히스토리. 입력된 줄을 위 화살표로 recall 한다. 사용자에게 readline config 가 있을 때만 세션 너머로 persist 된다.
- 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 는 같은 세션으로 연결된 엔진에서 카운터를 읽는
다는 뜻이다.
DDL audit 로깅
섹션 제목: “DDL audit 로깅”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 가 표시한다.
소스 워크스루
섹션 제목: “소스 워크스루”Launcher (csql_launcher.c)
섹션 제목: “Launcher (csql_launcher.c)”| 심볼 | 역할 |
|---|---|
main | 진입점; getopt 루프로 CSQL_ARGUMENT 채움; 검증 사슬; 엔진 라이브러리 dlopen/dlsym; 해석된 csql() 호출 |
utility_csql_usage | cubridsa 를 로드하고 csql_get_message 를 dlsym; 메시지 카탈로그에서 usage 렌더링 |
utility_csql_print | 메시지 카탈로그의 비-치명 launcher 메시지를 위한 가변인자 printer |
csql_option[] | 27 항목의 GETOPT_LONG 표 |
엔진 진입과 메인 루프 (csql.c)
섹션 제목: “엔진 진입과 메인 루프 (csql.c)”| 심볼 | 역할 |
|---|---|
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_csql | Read-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_complete | Lexer 상태에 대한 술어 |
csql_edit_contents_append / _clear / _finalize | submission 사이의 in-memory editor 버퍼 관리 |
csql_execute_statements | submission 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_no | SESSION_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_alltran | listing 을 위한 ;killtran formatter |
csql_pipe_handler | pager 가 세션을 죽이지 않게 하기 위한 SIGPIPE 핸들러 |
결과 렌더링 (csql_result.c, csql_result_format.c)
섹션 제목: “결과 렌더링 (csql_result.c, csql_result_format.c)”| 심볼 | 역할 |
|---|---|
csql_results | Top-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 formatters | per-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 |
Support 도우미 (csql_support.c)
섹션 제목: “Support 도우미 (csql_support.c)”| 심볼 | 역할 |
|---|---|
csql_walk_statement | Stateful lexer (start_csql 에서 줄마다 호출) |
csql_pipe_to_pager / csql_pipe_handler | Pager 통합 |
csql_get_message / csql_get_help_message | 메시지 카탈로그 accessor |
csql_check_server_down | read 루프 동안 주기적 probe — 서버 크래시 감지 후 offline prompt 로 전환 |
위치 힌트 (2026-05-05 기준)
섹션 제목: “위치 힌트 (2026-05-05 기준)”| 심볼 | 경로 |
|---|---|
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_csql | src/executables/csql.c:665 |
csql_exit_init | src/executables/csql.c:3032 |
csql_print_buffer | src/executables/csql.c:1927 |
csql_print_database | src/executables/csql.c:2592 |
csql_display_trace | src/executables/csql.c:3723 |
display_buffer | src/executables/csql.c:438 |
csql_Session_cmd_table[] | src/executables/csql_session.c:75 |
csql_get_session_cmd_no | src/executables/csql_session.c:150 |
csql_help_menu | src/executables/csql_session.c:206 |
SESSION_CMD (enum, S_CMD_*) | src/executables/csql.h:185 |
심볼 이름이 정규 anchor 이고, 라인 번호는 updated: 날짜에
스코프된 힌트이다.
Cross-check 노트
섹션 제목: “Cross-check 노트”- 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_fp가start_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 은 평행 표가 필요한데 존재하지 않는다.
Sources
섹션 제목: “Sources”src/executables/csql_launcher.c— argv 파서, 검증, 라이브러리 로더src/executables/csql.c— 엔진 진입, 메인 루프, 세션 명령 디스패처, 버퍼 관리, exit 처리src/executables/csql.h—CSQL_ARGUMENT구조체,SESSION_CMDenum, 메시지 상수, 함수 prototypesrc/executables/csql_session.c— 세션 명령 표와 prefix-match resolver,;help렌더링, killtran formattingsrc/executables/csql_result.c— 결과 iterator 와 paginationsrc/executables/csql_result_format.c—DB_VALUEtext formatter, per-type 렌더링, 출력 스타일 적용src/executables/csql_support.c— stateful per-line lexer (csql_walk_statement), pager 통합, 메시지 카탈로그 도우미, 서버-down probesrc/executables/AGENTS.md— csql 책임과 빌드-target 매핑을 명명한 agent 가이드 (CS-mode 바이너리는cs/CMakeLists.txt)- 인접 문서:
cubrid-coverage.md(utilities batch 안에서의 위치),cubrid-server-session.md(csqlclient_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가 의미를 갖는 컨텍스트)