Database 매뉴얼 · Chapter 5

title: "스크립트로 INSERT · UPDATE — 조리 시작/종료 기록" chapter: 5 images:

  • script-insert-update.png

스크립트로 INSERT · UPDATE

이 장부터 .xms 스크립트에서 데이터베이스를 다룹니다. 시나리오는 빌트인 order_history 테이블에 조리 시작 시 1행 INSERT, 조리 종료 시 같은 행 UPDATE 입니다.

스크립트로 조리 이력 기록

키 함수 두 가지

함수용도설명
RunSqlQuery(sql)INSERT · UPDATE · DELETE · DDL단순 실행. 성공 여부 bool
RunSqlQueryParam(sql, values)동일 + 파라미터 바인딩? 자리표시자에 XArray 값 매핑. SQL Injection 방지

운영 코드는 항상 RunSqlQueryParam 을 사용합니다. SQL 안에 변수 값을 직접 문자열로 합치는 방식은 따옴표 처리 실수와 인젝션 공격에 취약합니다.

1) BeginOrder — 조리 시작 시 INSERT

order_historystart_time 컬럼은 default 가 CURRENT_TIMESTAMP 이므로 INSERT 시점에 자동으로 채워집니다. 스크립트는 order_no, menu_name 만 넘기면 됩니다.

FUNCTION BeginOrder(string orderNo, string menuName)
{
   // ==========================================================================
   // Module : 'OrderHistory'
   // Caption : 조리 시작 시 order_history 행 추가
   //           start_time 은 default 로 자동 채워짐
   // Return  : bool  (true = 성공, false = 실패)
   // ==========================================================================
   if( STR.IsNullOrWhiteSpace(/*text*/orderNo) )
   {
      Log($"!!!---BeginOrder : orderNo is empty");
      return false;
   }
 
   XArray vals = XArray.Create1D(2);
   vals[0] = orderNo;
   vals[1] = menuName;
 
   string sql =
      "INSERT INTO order_history(order_no, menu_name)" +
      " VALUES(?, ?)";
 
   if( X.DB["local"].RunSqlQueryParam(sql, vals) == false )
   {
      LogError($"BeginOrder Error : {X.DB[\"local\"].LastError}");
      return false;
   }
 
   Log($"BeginOrder OK : orderNo={orderNo}, menu={menuName}");
   return true;
}

호출 예

BeginOrder(/*orderNo*/$"{SYS.DateString}_{seq:D03}", /*menuName*/"떡볶이");

seq 는 운영 측에서 발번해 주는 일련번호입니다. 날짜 + 일련번호 조합이면 식별이 쉽습니다.

2) EndOrder — 조리 종료 시 UPDATE

종료 시각, 계량 무게, 결과(OK/NG), 에러 플래그를 같은 행에 갱신합니다.

FUNCTION EndOrder(string orderNo, double weightG, string result, bool isError)
{
   // ==========================================================================
   // Module : 'OrderHistory'
   // Caption : 조리 종료 시 동일 order_no 행을 갱신
   // Return  : bool
   // ==========================================================================
 
   string endTime = SYS.GetDateTimeStringFormat("yyyy-MM-dd HH:mm:ss");
 
   XArray vals = XArray.Create1D(5);
   vals[0] = endTime;
   vals[1] = weightG;
   vals[2] = result;
   vals[3] = isError ? 1 : 0;
   vals[4] = orderNo;
 
   string sql =
      "UPDATE order_history" +
      "   SET end_time=?, weight_g=?, result=?, is_error=?" +
      " WHERE order_no=?";
 
   if( X.DB["local"].RunSqlQueryParam(sql, vals) == false )
   {
      LogError($"EndOrder Error : {X.DB[\"local\"].LastError}");
      return false;
   }
 
   Log($"EndOrder OK : orderNo={orderNo}, g={weightG}, r={result}");
   return true;
}

호출 예

EndOrder(/*orderNo*/orderNo,
         /*weightG*/UnitData::UnitResultWeight[unitIndex],
         /*result*/"OK",
         /*isError*/false);

파라미터 바인딩 규칙

RunSqlQueryParam 은 SQL 안의 ? 를 왼쪽부터 순서대로 XArray[0], [1], … 와 매핑합니다.

XArray vals = XArray.Create1D(3);
vals[0] = 42;          // INTEGER
vals[1] = 12.34;       // REAL
vals[2] = "hello";     // TEXT
 
X.DB["local"].RunSqlQueryParam(
   "INSERT INTO t(a, b, c) VALUES(?, ?, ?)", vals);
스크립트 타입DB 매핑
intINTEGER
doubleREAL
boolINTEGER (0/1)
stringTEXT
nullNULL

날짜는 항상 문자열 로 전달합니다. ISO 8601 (yyyy-MM-dd HH:mm:ss) 권장.

자주 묻는 질문

Q. INSERT 후 자동 부여된 id 값을 받고 싶습니다. 다음 한 줄을 이어서 호출하면 됩니다.

int newId = X.DB["local"].RunSqlScalarInt("SELECT last_insert_rowid()");
Log($"newId={newId}");

같은 트랜잭션 / 같은 연결에서 호출해야 정확한 값입니다.

Q. UPDATE 가 0건만 영향 줘도 성공으로 처리되나요? RunSqlQueryParam 은 SQL 실행 자체가 정상이면 true 를 반환합니다. 영향받은 행 수까지 확인하려면 RunSqlQueryNonQuery 를 쓰십시오 — 행 수를 int 로 반환하며 실패 시 -1.

int affected = X.DB["local"].RunSqlQueryNonQueryParam(sql, vals);
if( affected <= 0 )
{
   LogError($"EndOrder : no row matched - orderNo={orderNo}");
   return false;
}

Q. NULL 을 명시적으로 넣으려면? XArray 요소에 null 을 대입하거나 (스크립트 측에서 가능한 경우), SQL 자체에서 NULL 리터럴을 사용합니다. weight_g 처럼 default 가 0 인 컬럼은 “미입력” 과 “0” 을 구분하기 어려우므로, 구분이 필요한 컬럼은 default 를 두지 말고 NULL 허용으로 두는 편이 좋습니다.

학습 포인트

  • ? 자리표시자 + XArray 값 → 파라미터 바인딩 이 운영 코드의 표준 형태입니다.
  • 함수 헤더 표준 주석 을 빼먹지 마십시오. 운영 중 누가 언제 만든 코드인지 추적할 수 있어야 합니다.
  • 로그 두 줄을 항상 남깁니다 — 실패 시 LogError($"... : {LastError}"), 성공 시 Log($"... OK"). 이 둘이 있으면 8장의 OnDbError 이벤트와 함께 운영 중 원인 추적이 쉬워집니다.

체크포인트

  1. BeginOrder("test_001", "떡볶이") 호출 → Data 탭에서 행이 보인다.
  2. EndOrder("test_001", 105.4, "OK", false) 호출 → 같은 행의 end_time/weight_g/result 가 채워진다.
  3. 일부러 잘못된 컬럼명으로 INSERT 해보면 LastError 메시지에 SQLite 의 원인이 그대로 들어온다.

다음 단계

6장 — 스크립트로 SELECT · 트랜잭션