<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ko">
	<id>https://devcafe.co.kr/w/index.php?action=history&amp;feed=atom&amp;title=AutoIt%EC%97%90%EC%84%9C_sqlplus.exe_%ED%98%B8%EC%B6%9C_%EA%B2%B0%EA%B3%BC%EB%A5%BC_%EA%B7%B8%EB%A6%AC%EB%93%9C</id>
	<title>AutoIt에서 sqlplus.exe 호출 결과를 그리드 - 편집 역사</title>
	<link rel="self" type="application/atom+xml" href="https://devcafe.co.kr/w/index.php?action=history&amp;feed=atom&amp;title=AutoIt%EC%97%90%EC%84%9C_sqlplus.exe_%ED%98%B8%EC%B6%9C_%EA%B2%B0%EA%B3%BC%EB%A5%BC_%EA%B7%B8%EB%A6%AC%EB%93%9C"/>
	<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=AutoIt%EC%97%90%EC%84%9C_sqlplus.exe_%ED%98%B8%EC%B6%9C_%EA%B2%B0%EA%B3%BC%EB%A5%BC_%EA%B7%B8%EB%A6%AC%EB%93%9C&amp;action=history"/>
	<updated>2026-05-19T07:31:26Z</updated>
	<subtitle>이 문서의 편집 역사</subtitle>
	<generator>MediaWiki 1.42.1</generator>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=AutoIt%EC%97%90%EC%84%9C_sqlplus.exe_%ED%98%B8%EC%B6%9C_%EA%B2%B0%EA%B3%BC%EB%A5%BC_%EA%B7%B8%EB%A6%AC%EB%93%9C&amp;diff=2412&amp;oldid=prev</id>
		<title>Devcafe: 새 문서:  SET MARKUP CSV ON QUOTE ON으로 sqlplus 출력을 CSV로 받아 파싱 → JSON으로 변환하는 방식입니다. 자격증명은 conn.ini에 저장해서 HTTP body로 평문 전송하지 않도록 했습니다.  구조 추가 &lt;source lang=bash&gt;  project/ ├── main.au3 ├── conn.ini          ← 새로 추가 (접속정보) ├── sqlite3.dll └── web/index.html &lt;/source&gt;   conn.ini (예시) &lt;source lang=bash&gt;  [prod] user=scott pass=tiger tns=ORCL nls=KORE...</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=AutoIt%EC%97%90%EC%84%9C_sqlplus.exe_%ED%98%B8%EC%B6%9C_%EA%B2%B0%EA%B3%BC%EB%A5%BC_%EA%B7%B8%EB%A6%AC%EB%93%9C&amp;diff=2412&amp;oldid=prev"/>
		<updated>2026-05-18T10:16:31Z</updated>

		<summary type="html">&lt;p&gt;새 문서:  SET MARKUP CSV ON QUOTE ON으로 sqlplus 출력을 CSV로 받아 파싱 → JSON으로 변환하는 방식입니다. 자격증명은 conn.ini에 저장해서 HTTP body로 평문 전송하지 않도록 했습니다.  구조 추가 &amp;lt;source lang=bash&amp;gt;  project/ ├── main.au3 ├── conn.ini          ← 새로 추가 (접속정보) ├── sqlite3.dll └── web/index.html &amp;lt;/source&amp;gt;   conn.ini (예시) &amp;lt;source lang=bash&amp;gt;  [prod] user=scott pass=tiger tns=ORCL nls=KORE...&lt;/p&gt;
&lt;p&gt;&lt;b&gt;새 문서&lt;/b&gt;&lt;/p&gt;&lt;div&gt;&lt;br /&gt;
SET MARKUP CSV ON QUOTE ON으로 sqlplus 출력을 CSV로 받아 파싱 → JSON으로 변환하는 방식입니다. 자격증명은 conn.ini에 저장해서 HTTP body로 평문 전송하지 않도록 했습니다.&lt;br /&gt;
&lt;br /&gt;
구조 추가&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
&lt;br /&gt;
project/&lt;br /&gt;
├── main.au3&lt;br /&gt;
├── conn.ini          ← 새로 추가 (접속정보)&lt;br /&gt;
├── sqlite3.dll&lt;br /&gt;
└── web/index.html&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
conn.ini (예시)&lt;br /&gt;
&amp;lt;source lang=bash&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[prod]&lt;br /&gt;
user=scott&lt;br /&gt;
pass=tiger&lt;br /&gt;
tns=ORCL&lt;br /&gt;
nls=KOREAN_KOREA.AL32UTF8&lt;br /&gt;
&lt;br /&gt;
[dev]&lt;br /&gt;
user=hr&lt;br /&gt;
pass=hr&lt;br /&gt;
tns=XEPDB1&lt;br /&gt;
nls=KOREAN_KOREA.AL32UTF8&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
tns는 tnsnames.ora의 엔트리명, 또는 //host:port/service 형태도 가능.&lt;br /&gt;
&lt;br /&gt;
main.au3 에 추가 (상단 include 아래)&lt;br /&gt;
&amp;lt;source lang=autoit&amp;gt;&lt;br /&gt;
#include &amp;lt;Constants.au3&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Global $g_sConnIni = @ScriptDir &amp;amp; &amp;quot;\conn.ini&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Switch $sPath 안에 케이스 두 개 추가:&lt;br /&gt;
&lt;br /&gt;
Case &amp;quot;/api/oracle/connections&amp;quot;&lt;br /&gt;
    _ApiOraConns($iSock)&lt;br /&gt;
Case &amp;quot;/api/oracle/query&amp;quot;&lt;br /&gt;
    _ApiOraQuery($iSock, $sBody)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
그리고 파일 끝에 함수 추가:&lt;br /&gt;
&amp;lt;source lang=autoit&amp;gt;&lt;br /&gt;
&lt;br /&gt;
; ─────────────────────────────────────────────&lt;br /&gt;
; Oracle 연동&lt;br /&gt;
; ─────────────────────────────────────────────&lt;br /&gt;
Func _ApiOraConns($iSock)&lt;br /&gt;
    Local $aSec = IniReadSectionNames($g_sConnIni)&lt;br /&gt;
    Local $s = &amp;#039;{&amp;quot;ok&amp;quot;:true,&amp;quot;connections&amp;quot;:[&amp;#039;&lt;br /&gt;
    If IsArray($aSec) Then&lt;br /&gt;
        For $i = 1 To $aSec[0]&lt;br /&gt;
            If $i &amp;gt; 1 Then $s &amp;amp;= &amp;quot;,&amp;quot;&lt;br /&gt;
            $s &amp;amp;= _JsonStr($aSec[$i])&lt;br /&gt;
        Next&lt;br /&gt;
    EndIf&lt;br /&gt;
    $s &amp;amp;= &amp;#039;]}&amp;#039;&lt;br /&gt;
    _SendJson($iSock, $s)&lt;br /&gt;
EndFunc&lt;br /&gt;
&lt;br /&gt;
Func _ApiOraQuery($iSock, $sBody)&lt;br /&gt;
    Local $aSql  = StringRegExp($sBody, &amp;#039;&amp;quot;sql&amp;quot;:&amp;quot;((?:\\.|[^&amp;quot;\\])*)&amp;quot;&amp;#039;, 1)&lt;br /&gt;
    Local $aConn = StringRegExp($sBody, &amp;#039;&amp;quot;conn&amp;quot;:&amp;quot;((?:\\.|[^&amp;quot;\\])*)&amp;quot;&amp;#039;, 1)&lt;br /&gt;
    If Not IsArray($aSql) Or Not IsArray($aConn) Then&lt;br /&gt;
        _SendJson($iSock, &amp;#039;{&amp;quot;ok&amp;quot;:false,&amp;quot;err&amp;quot;:&amp;quot;sql/conn 누락&amp;quot;}&amp;#039;)&lt;br /&gt;
        Return&lt;br /&gt;
    EndIf&lt;br /&gt;
    Local $sSQL  = _JsonUnesc($aSql[0])&lt;br /&gt;
    Local $sName = _JsonUnesc($aConn[0])&lt;br /&gt;
&lt;br /&gt;
    Local $sUser = IniRead($g_sConnIni, $sName, &amp;quot;user&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
    Local $sPass = IniRead($g_sConnIni, $sName, &amp;quot;pass&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
    Local $sTns  = IniRead($g_sConnIni, $sName, &amp;quot;tns&amp;quot;,  &amp;quot;&amp;quot;)&lt;br /&gt;
    Local $sNls  = IniRead($g_sConnIni, $sName, &amp;quot;nls&amp;quot;,  &amp;quot;KOREAN_KOREA.AL32UTF8&amp;quot;)&lt;br /&gt;
    If $sUser = &amp;quot;&amp;quot; Or $sTns = &amp;quot;&amp;quot; Then&lt;br /&gt;
        _SendJson($iSock, &amp;#039;{&amp;quot;ok&amp;quot;:false,&amp;quot;err&amp;quot;:&amp;quot;conn.ini에 [&amp;#039; &amp;amp; $sName &amp;amp; &amp;#039;] 없음&amp;quot;}&amp;#039;)&lt;br /&gt;
        Return&lt;br /&gt;
    EndIf&lt;br /&gt;
&lt;br /&gt;
    ; 임시 SQL 스크립트&lt;br /&gt;
    Local $sTmp = @TempDir &amp;amp; &amp;quot;\au_oraq_&amp;quot; &amp;amp; @AutoItPID &amp;amp; &amp;quot;.sql&amp;quot;&lt;br /&gt;
    Local $sClean = StringRegExpReplace($sSQL, &amp;quot;[\r\n;\s]+$&amp;quot;, &amp;quot;&amp;quot;)&lt;br /&gt;
    Local $sScript = _&lt;br /&gt;
        &amp;quot;SET MARKUP CSV ON QUOTE ON&amp;quot; &amp;amp; @CRLF &amp;amp; _&lt;br /&gt;
        &amp;quot;SET FEEDBACK OFF&amp;quot;           &amp;amp; @CRLF &amp;amp; _&lt;br /&gt;
        &amp;quot;SET HEADING ON&amp;quot;             &amp;amp; @CRLF &amp;amp; _&lt;br /&gt;
        &amp;quot;SET PAGESIZE 50000&amp;quot;         &amp;amp; @CRLF &amp;amp; _&lt;br /&gt;
        &amp;quot;SET LINESIZE 32767&amp;quot;         &amp;amp; @CRLF &amp;amp; _&lt;br /&gt;
        &amp;quot;SET LONG 4000&amp;quot;              &amp;amp; @CRLF &amp;amp; _&lt;br /&gt;
        &amp;quot;SET TRIMSPOOL ON&amp;quot;           &amp;amp; @CRLF &amp;amp; _&lt;br /&gt;
        &amp;quot;SET SERVEROUTPUT OFF&amp;quot;       &amp;amp; @CRLF &amp;amp; _&lt;br /&gt;
        &amp;quot;WHENEVER SQLERROR EXIT 1&amp;quot;   &amp;amp; @CRLF &amp;amp; _&lt;br /&gt;
        $sClean &amp;amp; &amp;quot;;&amp;quot; &amp;amp; @CRLF &amp;amp; _&lt;br /&gt;
        &amp;quot;EXIT;&amp;quot; &amp;amp; @CRLF&lt;br /&gt;
&lt;br /&gt;
    Local $hF = FileOpen($sTmp, 2 + 128) ; overwrite + UTF-8&lt;br /&gt;
    FileWrite($hF, $sScript)&lt;br /&gt;
    FileClose($hF)&lt;br /&gt;
&lt;br /&gt;
    ; NLS_LANG 지정 후 sqlplus -S 실행&lt;br /&gt;
    EnvSet(&amp;quot;NLS_LANG&amp;quot;, $sNls)&lt;br /&gt;
    Local $sConnStr = $sUser &amp;amp; &amp;#039;/&amp;quot;&amp;#039; &amp;amp; $sPass &amp;amp; &amp;#039;&amp;quot;@&amp;#039; &amp;amp; $sTns&lt;br /&gt;
    Local $sCmd = &amp;#039;sqlplus -S -L &amp;#039; &amp;amp; $sConnStr &amp;amp; &amp;#039; @&amp;quot;&amp;#039; &amp;amp; $sTmp &amp;amp; &amp;#039;&amp;quot;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
    Local $iPID = Run(@ComSpec &amp;amp; &amp;#039; /c &amp;#039; &amp;amp; $sCmd, @ScriptDir, _&lt;br /&gt;
                     @SW_HIDE, $STDOUT_CHILD + $STDERR_CHILD)&lt;br /&gt;
    Local $sOut = &amp;quot;&amp;quot;, $sErr = &amp;quot;&amp;quot;&lt;br /&gt;
    While ProcessExists($iPID)&lt;br /&gt;
        $sOut &amp;amp;= StdoutRead($iPID)&lt;br /&gt;
        $sErr &amp;amp;= StderrRead($iPID)&lt;br /&gt;
        Sleep(30)&lt;br /&gt;
    WEnd&lt;br /&gt;
    $sOut &amp;amp;= StdoutRead($iPID)&lt;br /&gt;
    $sErr &amp;amp;= StderrRead($iPID)&lt;br /&gt;
    FileDelete($sTmp)&lt;br /&gt;
&lt;br /&gt;
    ; ORA-/SP2- 오류 감지&lt;br /&gt;
    Local $aOraErr = StringRegExp($sOut &amp;amp; @CRLF &amp;amp; $sErr, _&lt;br /&gt;
        &amp;quot;((?:ORA|SP2|TNS)-\d+[^\r\n]*)&amp;quot;, 1)&lt;br /&gt;
    If IsArray($aOraErr) Then&lt;br /&gt;
        _SendJson($iSock, &amp;#039;{&amp;quot;ok&amp;quot;:false,&amp;quot;err&amp;quot;:&amp;#039; &amp;amp; _JsonStr($aOraErr[0]) &amp;amp; &amp;#039;}&amp;#039;)&lt;br /&gt;
        Return&lt;br /&gt;
    EndIf&lt;br /&gt;
&lt;br /&gt;
    _SendJson($iSock, _CsvOutToJson($sOut))&lt;br /&gt;
EndFunc&lt;br /&gt;
&lt;br /&gt;
; sqlplus CSV 출력 → JSON&lt;br /&gt;
Func _CsvOutToJson($sRaw)&lt;br /&gt;
    Local $aLines = StringSplit(StringStripCR($sRaw), @LF)&lt;br /&gt;
    Local $aCols[0], $aRows[0]&lt;br /&gt;
    Local $bHeader = False&lt;br /&gt;
    For $i = 1 To $aLines[0]&lt;br /&gt;
        Local $sLine = $aLines[$i]&lt;br /&gt;
        If StringStripWS($sLine, 3) = &amp;quot;&amp;quot; Then ContinueLoop&lt;br /&gt;
        ; CSV 라인은 보통 &amp;quot;로 시작하거나 ,를 포함. 그 외 잡라인 스킵&lt;br /&gt;
        If StringLeft($sLine, 1) &amp;lt;&amp;gt; &amp;#039;&amp;quot;&amp;#039; And Not StringInStr($sLine, &amp;quot;,&amp;quot;) Then&lt;br /&gt;
            ContinueLoop&lt;br /&gt;
        EndIf&lt;br /&gt;
        Local $aFields = _ParseCsvLine($sLine)&lt;br /&gt;
        If Not $bHeader Then&lt;br /&gt;
            $aCols = $aFields&lt;br /&gt;
            $bHeader = True&lt;br /&gt;
        Else&lt;br /&gt;
            ReDim $aRows[UBound($aRows) + 1]&lt;br /&gt;
            $aRows[UBound($aRows) - 1] = $aFields&lt;br /&gt;
        EndIf&lt;br /&gt;
    Next&lt;br /&gt;
&lt;br /&gt;
    Local $s = &amp;#039;{&amp;quot;ok&amp;quot;:true,&amp;quot;columns&amp;quot;:[&amp;#039;&lt;br /&gt;
    For $i = 0 To UBound($aCols) - 1&lt;br /&gt;
        If $i &amp;gt; 0 Then $s &amp;amp;= &amp;quot;,&amp;quot;&lt;br /&gt;
        $s &amp;amp;= _JsonStr($aCols[$i])&lt;br /&gt;
    Next&lt;br /&gt;
    $s &amp;amp;= &amp;#039;],&amp;quot;rows&amp;quot;:[&amp;#039;&lt;br /&gt;
    For $i = 0 To UBound($aRows) - 1&lt;br /&gt;
        If $i &amp;gt; 0 Then $s &amp;amp;= &amp;quot;,&amp;quot;&lt;br /&gt;
        $s &amp;amp;= &amp;quot;[&amp;quot;&lt;br /&gt;
        Local $aRow = $aRows[$i]&lt;br /&gt;
        For $j = 0 To UBound($aRow) - 1&lt;br /&gt;
            If $j &amp;gt; 0 Then $s &amp;amp;= &amp;quot;,&amp;quot;&lt;br /&gt;
            $s &amp;amp;= _JsonStr($aRow[$j])&lt;br /&gt;
        Next&lt;br /&gt;
        $s &amp;amp;= &amp;quot;]&amp;quot;&lt;br /&gt;
    Next&lt;br /&gt;
    $s &amp;amp;= &amp;#039;]}&amp;#039;&lt;br /&gt;
    Return $s&lt;br /&gt;
EndFunc&lt;br /&gt;
&lt;br /&gt;
Func _ParseCsvLine($sLine)&lt;br /&gt;
    Local $aOut[256], $iN = 0&lt;br /&gt;
    Local $sCur = &amp;quot;&amp;quot;, $bQ = False&lt;br /&gt;
    Local $iLen = StringLen($sLine)&lt;br /&gt;
    For $i = 1 To $iLen&lt;br /&gt;
        Local $c = StringMid($sLine, $i, 1)&lt;br /&gt;
        If $bQ Then&lt;br /&gt;
            If $c = &amp;#039;&amp;quot;&amp;#039; Then&lt;br /&gt;
                If $i &amp;lt; $iLen And StringMid($sLine, $i + 1, 1) = &amp;#039;&amp;quot;&amp;#039; Then&lt;br /&gt;
                    $sCur &amp;amp;= &amp;#039;&amp;quot;&amp;#039;&lt;br /&gt;
                    $i += 1&lt;br /&gt;
                Else&lt;br /&gt;
                    $bQ = False&lt;br /&gt;
                EndIf&lt;br /&gt;
            Else&lt;br /&gt;
                $sCur &amp;amp;= $c&lt;br /&gt;
            EndIf&lt;br /&gt;
        Else&lt;br /&gt;
            If $c = &amp;#039;&amp;quot;&amp;#039; Then&lt;br /&gt;
                $bQ = True&lt;br /&gt;
            ElseIf $c = &amp;quot;,&amp;quot; Then&lt;br /&gt;
                $aOut[$iN] = $sCur&lt;br /&gt;
                $iN += 1&lt;br /&gt;
                $sCur = &amp;quot;&amp;quot;&lt;br /&gt;
            Else&lt;br /&gt;
                $sCur &amp;amp;= $c&lt;br /&gt;
            EndIf&lt;br /&gt;
        EndIf&lt;br /&gt;
    Next&lt;br /&gt;
    $aOut[$iN] = $sCur&lt;br /&gt;
    $iN += 1&lt;br /&gt;
    ReDim $aOut[$iN]&lt;br /&gt;
    Return $aOut&lt;br /&gt;
EndFunc&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
web/index.html — 툴바와 그리드 사이에 SQL 패널 추가&lt;br /&gt;
&lt;br /&gt;
&amp;lt;h2&amp;gt; 다음에 삽입:&lt;br /&gt;
&amp;lt;source lang=html&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;sqlPanel&amp;quot; style=&amp;quot;background:#fff;border:1px solid #ddd;&lt;br /&gt;
     border-radius:6px;padding:10px;margin-bottom:10px;&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;div style=&amp;quot;display:flex;gap:8px;align-items:center;margin-bottom:6px;&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;label&amp;gt;접속:&amp;lt;/label&amp;gt;&lt;br /&gt;
    &amp;lt;select id=&amp;quot;connSel&amp;quot; style=&amp;quot;padding:5px;&amp;quot;&amp;gt;&amp;lt;/select&amp;gt;&lt;br /&gt;
    &amp;lt;button onclick=&amp;quot;runSQL()&amp;quot;&amp;gt;▶ 실행 (Ctrl+Enter)&amp;lt;/button&amp;gt;&lt;br /&gt;
    &amp;lt;button onclick=&amp;quot;document.getElementById(&amp;#039;sqlPanel&amp;#039;).style.display=&amp;#039;none&amp;#039;&amp;quot;&amp;gt;접기&amp;lt;/button&amp;gt;&lt;br /&gt;
  &amp;lt;/div&amp;gt;&lt;br /&gt;
  &amp;lt;textarea id=&amp;quot;sqlArea&amp;quot; rows=&amp;quot;5&amp;quot;&lt;br /&gt;
    style=&amp;quot;width:100%;font-family:Consolas,monospace;font-size:13px;&lt;br /&gt;
           padding:6px;box-sizing:border-box;&amp;quot;&lt;br /&gt;
    placeholder=&amp;quot;SELECT * FROM dual&amp;quot;&amp;gt;&amp;lt;/textarea&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;button onclick=&amp;quot;document.getElementById(&amp;#039;sqlPanel&amp;#039;).style.display=&amp;#039;block&amp;#039;&amp;quot;&lt;br /&gt;
        style=&amp;quot;margin-bottom:8px;&amp;quot;&amp;gt;🔌 SQL&amp;lt;/button&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
그리고 &amp;lt;script&amp;gt; 안 loadAll() 위에 추가:&lt;br /&gt;
&amp;lt;source lang=html&amp;gt;&lt;br /&gt;
&lt;br /&gt;
async function loadConns(){&lt;br /&gt;
  const j = await (await fetch(&amp;#039;/api/oracle/connections&amp;#039;)).json();&lt;br /&gt;
  const sel = document.getElementById(&amp;#039;connSel&amp;#039;);&lt;br /&gt;
  sel.innerHTML = j.connections.map(c=&amp;gt;`&amp;lt;option&amp;gt;${c}&amp;lt;/option&amp;gt;`).join(&amp;#039;&amp;#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
async function runSQL(){&lt;br /&gt;
  const sql  = document.getElementById(&amp;#039;sqlArea&amp;#039;).value.trim();&lt;br /&gt;
  const conn = document.getElementById(&amp;#039;connSel&amp;#039;).value;&lt;br /&gt;
  if (!sql) return;&lt;br /&gt;
  setStatus(&amp;#039;Oracle 실행중...&amp;#039;);&lt;br /&gt;
  const t0 = Date.now();&lt;br /&gt;
  const r = await fetch(&amp;#039;/api/oracle/query&amp;#039;, {&lt;br /&gt;
    method:&amp;#039;POST&amp;#039;,&lt;br /&gt;
    headers:{&amp;#039;Content-Type&amp;#039;:&amp;#039;application/json&amp;#039;},&lt;br /&gt;
    body: JSON.stringify({sql, conn})&lt;br /&gt;
  });&lt;br /&gt;
  const j = await r.json();&lt;br /&gt;
  if (!j.ok){&lt;br /&gt;
    setStatus(&amp;#039;❌ &amp;#039; + j.err);&lt;br /&gt;
    alert(j.err);&lt;br /&gt;
    return;&lt;br /&gt;
  }&lt;br /&gt;
  // 그리드에 결과 표시: 1행=헤더, 이후=데이터&lt;br /&gt;
  const data = [j.columns, ...j.rows];&lt;br /&gt;
  // 빈 행 패딩&lt;br /&gt;
  while (data.length &amp;lt; 30) data.push(Array(j.columns.length).fill(&amp;#039;&amp;#039;));&lt;br /&gt;
  const el = document.getElementById(&amp;#039;sheet&amp;#039;);&lt;br /&gt;
  el.innerHTML = &amp;#039;&amp;#039;;&lt;br /&gt;
  sheet = jspreadsheet(el, {&lt;br /&gt;
    data,&lt;br /&gt;
    columns: j.columns.map((c,i)=&amp;gt;({title:String.fromCharCode(65+i), width:130})),&lt;br /&gt;
    minDimensions: [Math.max(j.columns.length,10), Math.max(data.length,30)]&lt;br /&gt;
  });&lt;br /&gt;
  setStatus(`✅ ${j.rows.length}행 / ${((Date.now()-t0)/1000).toFixed(2)}s`);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Ctrl+Enter 단축키&lt;br /&gt;
document.addEventListener(&amp;#039;keydown&amp;#039;, e=&amp;gt;{&lt;br /&gt;
  if (e.ctrlKey &amp;amp;&amp;amp; e.key === &amp;#039;Enter&amp;#039; &amp;amp;&amp;amp;&lt;br /&gt;
      document.activeElement.id === &amp;#039;sqlArea&amp;#039;){&lt;br /&gt;
    e.preventDefault(); runSQL();&lt;br /&gt;
  }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
loadConns();&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
확인 / 주의사항&lt;br /&gt;
&lt;br /&gt;
	•	sqlplus PATH: Oracle Client 또는 Instant Client의 sqlplus.exe가 PATH에 있어야 함. 없으면 Run 호출에서 sqlplus 대신 전체 경로(C:\app\client\bin\sqlplus.exe) 사용.&lt;br /&gt;
	•	NLS_LANG: 한글 깨지면 conn.ini의 nls 값을 DB 캐릭터셋에 맞게 조정. UTF-8 DB면 AL32UTF8, KO16MSWIN949 DB면 KOREAN_KOREA.KO16MSWIN949 + 프론트에서도 인코딩 매칭 필요.&lt;br /&gt;
	•	단일 SELECT 전용: 현재 구조는 SELECT 결과 표시용. DML/DDL은 결과 행이 없어 grid가 비게 됨 — 필요 시 SET FEEDBACK ON으로 두고 raw 출력을 메시지로 반환하는 분기 추가 가능.&lt;br /&gt;
	•	세미콜론 처리: 입력 SQL 끝의 ;은 정규식으로 제거 후 한 번만 붙임. PL/SQL 블록(BEGIN ... END;)은 별도 처리 필요 — 현재는 단일 SELECT 기준.&lt;br /&gt;
	•	결과 크기: 매우 큰 결과셋은 stdout 버퍼/메모리 부담. 운영용은 WHERE ROWNUM &amp;lt;= n 또는 FETCH FIRST n ROWS ONLY로 제한 권장.&lt;br /&gt;
	•	conn.ini 보안: 평문 비밀번호이므로 NTFS 권한으로 본인만 읽기 가능하게 설정하거나, AutoIt에서 간단 XOR 암호화 래퍼를 씌우는 것도 가능.&lt;br /&gt;
&lt;br /&gt;
PL/SQL 블록 지원이나 DML 결과 표시, 컬럼별 데이터 타입(NUMBER/DATE) 우측정렬·포맷 등이 필요하면 이어서 만들어 드릴게요.&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
</feed>