Extra Form
PHP PHP 7.4
CMS Rhymix 2.1

아래의 코드와 같이 직접 쿼리를 날리는 데 성공은 했는데, 역시나 모듈 같은 서드파티 공개용 자료에서는 xml 파일로 옮기는 게 멋진 거 같아서요. (왜일까요ㅋㅋㅋ)

간혹 보면 소개되지 않은 구문이나 연산자가 xml로 구현 가능하기도 하던데 정말 혹시에 혹시나 해서 가능한지 문의해봅니다.

 

디버그하면 결과는 잘 나옵니다. 로그인한 자신과 쪽지를 나눈 상대가 누구인지(partner_srl)를 목록으로 만들어주고, 거기에 가장 최근의 메시지 번호와 등록일(last_message_srl, last_regdate), 상대와 나눈 쪽지 개수(message_count), 상대와 처음 나눴던 메시지 번호와 등록(first_message_srl, first_regdate), 상대와의 대화 중 아직 읽지 않은 쪽지 개수(unread_count) 등을 합쳤습니다.

정말 xml로 옮길 방법 없을까요?

에효... 뭔가 쓸데없이 헤매고 있는 것 같고 그렇습니다.

 

$member_srl = Context::get('logged_info')->member_srl;
$page = max(1, intval(Context::get('page')));
$list_count = 10;
$limit = ($page > 1) ? $list_count * ($page - 1) . ', ' . $list_count : $list_count;

$oDB = DB::getInstance();
$query =
    'SELECT CASE WHEN message_type = ? THEN receiver_srl ELSE sender_srl END AS partner_srl,
        MAX(message_srl) AS last_message_srl,
        MAX(regdate) AS last_regdate,
        COUNT(message_srl) AS message_count,
        MIN(message_srl) AS first_message_srl,
        MIN(regdate) AS first_regdate,
        SUM(CASE WHEN readed = ? THEN 1 ELSE 0 END) AS unread_count
    FROM member_message
    WHERE ((receiver_srl = ? AND message_type IN (?, ?)) OR (sender_srl = ? AND message_type = ?))
    GROUP BY partner_srl
    ORDER BY last_message_srl DESC
    LIMIT ?';
$cond_arg = ['S', 'N', $member_srl, 'R', 'T', $member_srl, 'S', $limit];
$stmt = $oDB->query($query, $cond_arg);
$result = $stmt->fetchAll();

 

 

스크린샷 2023-12-20 212256.png

  • profile
    xml으로 제공되지 않는 옵션들은 결국 따로 쓰셔야 합니다 ㅠ
    https://rhymix.org/manual/plugin/dbquery/operation
    여기에서 제공해주는거랑
    https://github.com/rhymix/rhymix/pull/1332

    여기에서 안내해주고 있는 업데이트 사항 말고는 개발이 안되어있으니 직접 쿼리문을 작성해야합니다.

    그래도 파라미터형식으로 작성해주셔서 인젝션 공격방어도 되어있고 그래서 그렇게 쓰시는게 좋을듯 하네요 :)
  • profile profile

    역시... ㅜㅜ
    정보 감사합니다. 정규식도 사용이 가능했었군요. 대박이에요 @.@
    근데 코어에서 직접 쿼리를 날린 경우는 없겠죠?

  • profile profile
    코어에서는 보통 xml작동 이외 복잡한 쿼리를 할일이 거의 없어서.. ㅋㅋㅋ 없는것 같아요 제가 코어 코드리뷰를 전부 하는게 아니라서 혹시나 숨어잇을 수 잇긴해요
  • profile

    참고로 위의 코드는 아래와 같이 수정됐습니다.

    $page = max(1, intval(Context::get('page')));
    $list_count = 10;
    $total_list_count = ($page > 1) ? $list_count * ($page - 1) : null;
    
    $oDB = DB::getInstance();
    $query =
        'SELECT CASE WHEN message_type = ? THEN receiver_srl ELSE sender_srl END AS partner_srl,
            MAX(message_srl) AS last_message_srl,
            MAX(regdate) AS last_regdate,
            COUNT(message_srl) AS message_count,
            MIN(message_srl) AS first_message_srl,
            MIN(regdate) AS first_regdate,
            SUM(CASE WHEN readed = ? THEN 1 ELSE 0 END) AS unread_count
        FROM member_message
        WHERE ((receiver_srl = ? AND message_type IN (?, ?)) OR (sender_srl = ? AND message_type = ?))
        GROUP BY partner_srl
        ORDER BY last_message_srl DESC
        LIMIT ?, ?';
    $cond_arg = ['S', 'N', $member_srl, 'R', 'T', $member_srl, 'S', $total_list_count, $list_count];
    $stmt = $oDB->query($query, $cond_arg);
    $result = $stmt->fetchAll();

     

  • profile
    <query id="getWhateverFoobar" action="select">
        <tables>
            <table name="member_message" />
        </tables>
        <columns>
            <column name="CASE WHEN message_type = 'S' THEN receiver_srl ELSE sender_srl END" alias="partner_srl" />
            <column name="MIN(message_srl)" alias="first_message_srl" />
            <column name="MAX(message_srl)" alias="last_message_srl" />
            <column name="MIN(regdate)" alias="first_regdate" />
            <column name="MAX(regdate)" alias="last_regdate" />
            <column name="COUNT(message_srl)" alias="message_count" />
            <column name="SUM(CASE WHEN readed = 'N' THEN 1 ELSE 0 END)" alias="unread_count" />
        </columns>
        <conditions>
            <group>
                <condition operation="equal" column="receiver_srl" var="member_srl" />
                <condition operation="in" column="message_type" default="R,T" />
            </group>
            <group pipe="or">
                <condition operation="equal" column="sender_srl" var="member_srl" />
                <condition operation="equal" column="message_type" default="S" />
            </group>
        </conditions>
        <groups>
            <group column="partner_srl" />
        </groups>
        <navigation>
            <index var="sort_index" default="last_message_srl" order="desc" />
            <list_count var="list_count" default="10" />
        </navigation>
    </query>

     

    'S', 'N' 등 보안상 위협이 되지 않는 고정된 문자열을 하드코딩한다면 충분히 가능합니다. 위의 쿼리라면 member_srl과 list_count만 넘기면 되고, 필요시 message_type 같은 것들도 변수로 처리할 수도 있겠네요.

     

    물론, 원본 소스처럼 변수 파라미터 처리를 철저하게 한다면 XML이 아닌 커스텀 쿼리를 쓰더라도 아무 문제가 없습니다. XML 쿼리는 보안성 외에도 재사용성 면에서 장점이 있지만, 코어에 포함된 공통 모듈이 아니라 특정 서드파티 자료에서만 사용할 쿼리라면 재사용성이 그다지 중요하지도 않고요.

     

    그룹핑과 정렬에 사용하는 컬럼이 모두 계산식이어서 절대 인덱스를 탈 수 없다는 것이 이 쿼리의 유일한, 그리고 어쩌면 치명적인, 단점이죠.^^

  • profile profile

    재사용성이 중요한 차이군요. 명확하게 짚어주셔서 개념이 잡히는 느낌입니다.

     

    컬럼 네임에 그냥 넣어주면 되는군요.

    CASE 같은 게 들어갈리가 없다고 생각했는데 ㅎㄷㄷ합니다.

    xml 쿼리가 되니까 단번에 코드 전체가 깔끔해졌습니다.

    사실 저렇게 뽑아낸 다음에도 count 추출하는 쿼리 또 만들고 그걸 가지고 리스트 아이템마다 $no 처리해주고 그랬거든요ㅜㅜ

     

    ... 그리고 마지막 짚어주신 단점을 고려한다면 역시 별도의 테이블을 따로 생성해서 접근하는 게 좋은 거죠?

    count 쿼리타임이 0.0012초, 리스트 쿼리타임이 0.0010초. 역시 개운하지만은 않네요ㅎㅎㅎ

  • profile profile

    alias만 제대로 지정해 주면 웬만한 표현은 다 넣을 수 있습니다.

     

    테스트 서버에서의 쿼리 속도는 무의미합니다. 인덱스를 효율적으로 사용할 수 없는 쿼리의 소요시간은 데이터 분량에 비례하기 때문이죠. 쪽지가 100만 건쯤 쌓여 있고 그 중 20%가 관리자에게 집중되었다고 가정할 때 (신고알림, 각종 도움 요청 등), 관리자의 쪽지함을 불러오는 데 시간이 얼마나 걸릴지 궁금합니다.

    메뉴 새글 표시 모듈, 아이템샵 등 나름 괜찮은 기능인데도 소형 커뮤니티에서나 쓸 수 있는 구조로 설계되어, 사이트 규모가 커지면 폐기를 고민하게 되는 자료가 꽤 많습니다. 캐시를 좀 활용해 보라고 했더니 게시판의 모든 글을 캐시에 넣어놓으려고 하는 정신나간 자료도 봤고요. ㅎㅎ