개발 중 자주 쿼리를 확인하는 일이 있다. 그런데 ->toSql()로 하게 되면 바인딩 전 쿼리만 나와서 해당 값이 정상 들어갔는지 확인이 어려울 수 있다.

이번에는 mysql 4.x old 버전에 쿼리 질의를 할 일이 생겼는데, php 5.x 버전 부터 mysql 4.x 버전을 지원하지 않고, 어느 언어를 사용하더라도 4.x 버전을 미지원이라는 장벽을 맞이하게 되었다. 결국 원격으로 php 5.x 저 버전에 raw 쿼리를 던져서 질의 하도록 미들웨어 개념의 api를 만들었다. 기본 어드민 툴은 laravel에서 사용을 해야 하니 raw query를 생성해서 던지는 작업을 해야하는 경우가 생긴 것이다.

머 이유가 어찌 됐건 일단 바인딩된 최종 raw query 획득을 위한 작업을 진행하기 위해서 설정 작업이 필요하다. Laravel에서 eloquent orm 이나 query builder로 쿼리를 실행하게 되면 QueryExecuted 이벤트가 발생을 한다. 해당 이벤트 발생시 실행된 쿼리를 수집하기 위해서는 AppServiceProvider.php에 추가를 한다.

Event 추가

// app/Provides/AppServiceProvider.php

use App\Http\Models\Query; // 추가한 Query class

    public function register()
    {
        //
        \Event::listen('Illuminate\Database\Events\QueryExecuted', function ($query) {
            Query::set($query);
        });
    }

 

Query Class 추가

class Query
{
    static $query;

    //
    static function get()
    {
        return self::debug_query();
    }

    static function set($query)
    {
        self::$query = $query;
    }
    
     /**
     * query build 내용을 raw query로 변경
     * @param $query
     * @return mixed|string
     */
    static function debug_query() {
        $queryRaw = self::$query;

        $query = vsprintf(str_replace('?', '%s', $queryRaw->sql), collect($queryRaw->bindings)->map(function ($binding) {
            return is_numeric($binding) ? $binding : "'{$binding}'";
        })->toArray());

        $double_linebreak_words = ['(', ')'];
        $double_linebreak_words_replace = array_map(function($str){ return PHP_EOL . $str . PHP_EOL; }, $double_linebreak_words);
        $query = str_replace($double_linebreak_words, $double_linebreak_words_replace, $query);


        $mysql_keywords = ['ADD', 'ALL', 'ALTER', 'ANALYZE', 'AND', 'AS', 'ASC', 'AUTO_INCREMENT', 'BDB', 'BERKELEYDB', 'BETWEEN', 'BIGINT', 'BINARY', 'BLOB', 'BOTH', 'BTREE', 'BY', 'CASCADE', 'CASE', 'CHANGE', 'CHAR', 'CHARACTER', 'CHECK', 'COLLATE', 'COLUMN', 'COLUMNS', 'CONSTRAINT', 'CREATE', 'CROSS', 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'DATABASE', 'DATABASES', 'DAY_HOUR', 'DAY_MINUTE', 'DAY_SECOND', 'DEC', 'DECIMAL', 'DEFAULT', 'DELAYED', 'DELETE', 'DESC', 'DESCRIBE', 'DISTINCT', 'DISTINCTROW', 'DIV', 'DOUBLE', 'DROP', 'ELSE', 'ENCLOSED', 'ERRORS', 'ESCAPED', 'EXISTS', 'EXPLAIN', 'FALSE', 'FIELDS', 'FLOAT', 'FOR', 'FORCE', 'FOREIGN', 'FROM', 'FULLTEXT', 'FUNCTION', 'GEOMETRY', 'GRANT', 'GROUP', 'HASH', 'HAVING', 'HELP', 'HIGH_PRIORITY', 'HOUR_MINUTE', 'HOUR_SECOND', 'IF', 'IGNORE', 'INDEX', 'INFILE', 'INNER', 'INNODB', 'INSERT', 'INTEGER', 'INTERVAL', 'INTO', 'JOIN', 'KEY', 'KEYS', 'KILL', 'LEADING', 'LEFT', 'LIKE', 'LIMIT', 'LINES', 'LOAD', 'LOCALTIME', 'LOCALTIMESTAMP', 'LOCK', 'LONG', 'LONGBLOB', 'LONGTEXT', 'LOW_PRIORITY', 'MASTER_SERVER_ID', 'MATCH', 'MEDIUMBLOB', 'MEDIUMINT', 'MEDIUMTEXT', 'MIDDLEINT', 'MINUTE_SECOND', 'MOD', 'MRG_MYISAM', 'NATURAL', 'NOT', 'NULL', 'NUMERIC', 'ON', 'OPTIMIZE', 'OPTION', 'OPTIONALLY', 'ORDER', 'OUTER', 'OUTFILE', 'PRECISION', 'PRIMARY', 'PRIVILEGES', 'PROCEDURE', 'PURGE', 'READ', 'REAL', 'REFERENCES', 'REGEXP', 'RENAME', 'REPLACE', 'REQUIRE', 'RESTRICT', 'RETURNS', 'REVOKE', 'RIGHT', 'RLIKE', 'RTREE', 'SELECT', 'SET', 'SHOW', 'SMALLINT', 'SOME', 'SONAME', 'SPATIAL', 'SQL_BIG_RESULT', 'SQL_CALC_FOUND_ROWS', 'SQL_SMALL_RESULT', 'SSL', 'STARTING', 'STRAIGHT_JOIN', 'STRIPED', 'TABLE', 'TABLES', 'TERMINATED', 'THEN', 'TINYBLOB', 'TINYINT', 'TINYTEXT', 'TO', 'TRAILING', 'TRUE', 'TYPES', 'UNION', 'UNIQUE', 'UNLOCK', 'UNSIGNED', 'UPDATE', 'USAGE', 'USE', 'USER_RESOURCES', 'USING', 'VALUES', 'VARBINARY', 'VARCHAR', 'VARCHARACTER', 'VARYING', 'WARNINGS', 'WHEN', 'WHERE', 'WITH', 'WRITE', 'XOR', 'YEAR_MONTH', 'ZEROFILL', 'INT', 'OR', 'IS', 'IN'];
        $mysql_keywords = array_map(function($str){ return " $str "; }, $mysql_keywords);
        $mysql_keywords_lc = array_map(function($str){ return strtolower($str); }, $mysql_keywords);
        $query = str_replace($mysql_keywords_lc, $mysql_keywords, $query);


        $linebreak_before_words = ['INNER JOIN', 'LEFT JOIN', 'OUTER JOIN', 'RIGHT JOIN', 'WHERE', 'FROM', 'GROUP BY', 'SELECT'];
        $linebreak_before_words_replace = array_map(function($str){ return PHP_EOL . $str; }, $linebreak_before_words);
        $query = str_replace($linebreak_before_words, $linebreak_before_words_replace, $query);


        $linebreak_after_words = [','];
        $linebreak_after_words_replace = array_map(function($str){ return $str . PHP_EOL; }, $linebreak_after_words);
        $query = str_replace($linebreak_after_words, $linebreak_after_words_replace, $query);

        $query = str_replace('select ', 'SELECT ', $query);

        return $query;
    }
 }

사용 방법

namespace App\Http\Models;

use User;
use Illuminate\Database\Eloquent\Model;

class test extends Model
{
    public index()
    {
        $user = new User;
        
        $user->first();
        
        $query = Query::get();
        
        echo $query;
    }
}

결과

SELECT * FROM `users` LIMIT 1

 

 

 


WRITTEN BY
비트센스
뷰파인더로 보는 프로그래머의 세상 페이스북 @bitsense 트위터 @picory 스카이프 picory MSN drawhalf@dreamwiz.com

트랙백  0 , 댓글  0개가 달렸습니다.
secret

n일 이전 생성 파일 보기 및 삭제

# n일 이전 생성 파일 보기
find 폴더/ -mtime +n
find 폴더/ -mtime +n -print

# n일 이전 생성 파일 삭제
find 폴더/ -mtime +n -delete
find 폴더/ -mtime +n -exec rm {} \;

WRITTEN BY
비트센스
뷰파인더로 보는 프로그래머의 세상 페이스북 @bitsense 트위터 @picory 스카이프 picory MSN drawhalf@dreamwiz.com

트랙백  0 , 댓글  0개가 달렸습니다.
secret

요 근래 말이 많은 텔레그램입니다. 원격지 컴퓨터에 명령 실행할 방법을 찾다가, 메신저를 통해서 실행해 보자는 아이디어 차원에서 한번 챗봇을 보게 되었는데, 파이썬 + 텔레그램 봇 구성이 생각보다 쉬워 공유를 해봅니다.

telepot 모듈 설치

pip install telepot --upgrade

소스 샘플

import telepot

from telepot.loop import MessageLoop
import time

TOKEN_MAIN = 'BOT_TOKEN'
StartMsg = """
BOT 기본 명령어
1. /help : 도움말
2. 안녕
"""

# 특정 명령어가 입력할 때 반응
def execcommand(message, chat_id):
    args = message.split(' ')
    command = args[0]
    del args[0]

    if command == '/help':
        send(chat_id, StartMsg)

# 메시지를 그대로 전송
def echoserver(message, chat_id, target):
    args = message.split(' ')
    command = args[0]

    if command == '안녕':
        send(chat_id, '안녕하세요~ %s 님!' % target['username'])

# 메시지 텔레그램으로 전송
def send(chat_id, message):
    bot.sendMessage(chat_id, message)

def handler(msg):
    content_type, chat_type, chat_id, msg_date, msg_id = telepot.glance(msg, long=True)

    print(msg)
    if content_type == 'text':
        _message = msg['text']
        if _message[0:1] == '/': # 명령어
            execcommand(_message, chat_id)
        else:
            echoserver(_message, chat_id, msg['from'])

bot = telepot.Bot(TOKEN_MAIN)
bot.message_loop(handler, run_forever=True)

버그 리포트

#1. ssl 접속 오류

urllib3 버전 이슈로 보입니다. 1.25 이상 버전에서 자체 서명된 인증서(self signed cert) 인 경우, SSL 인증서를 무시하는 부분을 거절하네요. 그래서 urllib3 버전을 강제로 다운그레이드 합니다.

# urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='api.telegram.org', port=443): 
pip install urllib3==1.24.3

참조 URL

https://telepot.readthedocs.io/en/latest/

 


WRITTEN BY
비트센스
뷰파인더로 보는 프로그래머의 세상 페이스북 @bitsense 트위터 @picory 스카이프 picory MSN drawhalf@dreamwiz.com

트랙백  0 , 댓글  0개가 달렸습니다.
secret

머신러닝 같은 강좌나 책을 읽다 보면 간혹 마주치는 단어가 시계열 혹은 시계열화 이란 것입니다. 말은 어려워 보이는데 영어로는 "time series" 라고 합니다. 한글은 어렵지만 영어는 어느 정도 뜻이 이해가 갑니다. 그냥 순차적으로 먼가 하는 거구나... 라는 센쑤~!

개발자들이 디비 등에서 추출한 정보는 보통 테이블 형식의 텍스트로 제공하는 경우가 많습니다. 이것을 보기 좋게 그래프 형태로 변형해서 보여주면, 직관적으로 이해하게 되고, 실시간 의사결정이 가능해 진다고 합니다. 예전부터 알고는 있었지만, 막상 그래프 표현은 좀 어렵기는 합니다.

vuejs에서는 되게 쉽습니다. 시계열의 대표적인 것이 Line Chart 일 것 같아서, 소스로 좀더 쉽게 만들어 보도록 하겠습니다. Line Charts 는 아래와 같은 형태 입니다.

설치

// 설치
npm i --save vue-chartjs chart.js

LineChart 템플릿 소스 샘플

// 공용 사용 가능성이 있어서 components/Charts 에 컴포넌트 형태로 저장하여 사용
// data = chartData 외부에서 가져옴
// chartData = {labels: ['전체 범주'], data: { label: '그래프 범주', data: []}
<script>
    import {Line} from 'vue-chartjs'

    export default {
        name: 'LineChart',
        extends: Line,
        props: ['chartData'],
        data: () => ({
                colorSets: [ // 여러 그래프 사용을 위해서 색상표 예약
                    {fore: '#EF9A9A', back: '#B71C1C'},
                    {fore: '#F48FB1', back: '#880E4F'},
                    {fore: '#CE93D8', back: '#4A148C'},
                    {fore: '#B39DDB', back: '#311B92'},
                    {fore: '#9FA8DA', back: '#1A237E'},
                    {fore: '#64B5F6', back: '#0D47A1'},
                    {fore: '#4FC3F7', back: '#01579B'},
                    {fore: '#0097A7', back: '#006064'},
                    {fore: '#00897B', back: '#004D40'},
                    {fore: '#81C784', back: '#1B5E20'},
                ],
                datacollection: { // 데이터 샘플
                    // 전체 범주
                    labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
                    datasets: [
                        {
                        	// 그래프별 범주
                            label: 'Data One',
                            backgroundColor: '#f87979',
                            pointBackgroundColor: 'white',
                            borderWidth: 1,
                            pointBorderColor: '#249EBF',
                            // 실제 데이터. labels와 배열 순서가 맞아야 함. 빈곳은 0으로 보정이 필요
                            data: [40, 20, 30, 50, 90, 10, 20, 40, 50, 70, 90, 100]
                        }
                    ]
                },
                options: {
                    scales: {
                        yAxes: [{
                            ticks: {
                                beginAtZero: true
                            },
                            gridLines: {
                                display: true
                            }
                        }],
                        xAxes: [{
                            gridLines: {
                                display: false
                            }
                        }]
                    },
                    legend: {
                        display: true
                    },
                    responsive: true,
                    maintainAspectRatio: false
                }
            }
        ),
        mounted() {
            this.init()
        },
        methods: {
            init() {
                let data = this.chartData.chartData

                let datasets = []
                let pos = 0
                data.forEach((site) => {
                    let colors = this.colorSets[pos]

                    datasets.push({
                        label: site.label,
                        borderWidth: 2,
                        borderColor: colors.back,
                        backgroundColor: colors.back,
                        pointBorderColor: colors.fore,
                        pointBackgroundColor: colors.fore,
                        fill: false,
                        data: site.data
                    })

                    pos++
                })

                this.datacollection = {
                    labels: this.chartData.labels,
                    datasets: datasets
                }

                this.render()
            },
            render() {
                this.renderChart(this.datacollection, this.options)
            }

        }
    }
</script>

<style scoped>

</style>

vuejs 샘플 소스

<template>
    <v-container fluid>
        <v-row>
          <v-col>
            <div class="text-lg-center pa-5" style="width: 100%;" v-if="chartLoading">
              <v-progress-circular
                  width="7"
                  size="70"
                  indeterminate
                  color="red"
              ></v-progress-circular>
            </div>
            <line-chart :chartData="chartData" v-if="!chartLoading"/>
          </v-col>
        </v-row>
    </v-container>
</template>

<script>
    // '@/components/Charts/LineChart' 사용 가능하면 대체를 해도 된다.
    import LineChart from '../Charts/LineChart'

    export default {
        name: 'List',
        components: { LineChart },
        data: () => ({
            chartLoading: false, // 데이터를 불러오기 전까지는 progress circle을 사용
            chartData: []
        }),
        mounted() {
            this.init()
        },
        methods: {
            init() {
                let item = '{"labels":["01","02","03","04","05","06","07","08","09","10","11"],"chartData":[{"label":"다음","data":["65","13","22","125","41","142","156","121","24","29","151"]},{"label":"다우존스","data":["1","1","0","6","1","3","6","8","0","0","6"]},{"label":"네이버","data":["65","13","22","119","41","139","150","119","20","28","147"]},{"label":"뉴스원","data":["61","7","17","105","28","128","138","108","10","20","137"]},{"label":"다나와","data":["0","0","0","1","0","0","1","0","0","0","0"]}]}'
            	let data = JSON.parse(item)
                
                this.chartData = {
                    labels: data.labels,
                    chartData: data.chartData
                }

                // 차트 보이기
                this.chartLoading = false
            }
    }
</script>

<style scoped>
 
</style>

 

소스 샘플 결과


WRITTEN BY
비트센스
뷰파인더로 보는 프로그래머의 세상 페이스북 @bitsense 트위터 @picory 스카이프 picory MSN drawhalf@dreamwiz.com

트랙백  0 , 댓글  0개가 달렸습니다.
secret

관련 세미나 동영상: https://www.youtube.com/watch?v=BlEzbGTQ9Zs

우아한테크세미나 동영상 자료

2020년 5월 7일 개최한 "우아한테크세미나" 발표 내용은 아래와 같습니다.

발표자료

1부 - 엔티티 클래스 설계와 퍼시스턴스 프레임워크의 활용
* 발표자료 (원페이지뷰) :
https://bit.ly/2YNxlV7
* 발표자료 (슬라이드) :
https://benelog.github.io/entity-dev/

2부 - Spring Data JDBC Advanced
* 발표자료 :
https://bit.ly/3fCkRpg
* 참고코드 :
https://bit.ly/2SFjwE8www.bit.ly/2YNxlV7

참 멋지네요.

 


WRITTEN BY
비트센스
뷰파인더로 보는 프로그래머의 세상 페이스북 @bitsense 트위터 @picory 스카이프 picory MSN drawhalf@dreamwiz.com

트랙백  0 , 댓글  0개가 달렸습니다.
secret

오늘 오전에 애플에서 받은 메일입니다. WWDC이 계절이 다가오고 있습니다. 벌써~~~!!!

애플에서 받은 WWDC 2020 관련 메일

애플의 가장 큰 개발자 행사인 WWDC 2020이 코로나 영향으로 6월 22일 온라인으로 진행을 한다고 합니다.

관련 링크는 개발자 페이지로 이동하구요. https://developer.apple.com/wwdc20/?cid=CDM-DM-P0012725-444312&cp=em-P0012725-444312&sr=em

아바타를 활용해서 온라인으로 듣고 있는 모습을 연출했는데 애니 같은 느낌입니다.

앱 스토어를 통해서 "Apple Developer" 라는 앱을 배포 중입니다. 이곳에서 관련 강좌 등의 내용을 지속 업데이트 할 예정으로 보입니다.

아이패드용 앱 실행 화면

이번 컨퍼런스의 특이사항은 Swift 관련 학생 대회?(Swift Student Challenge)를 5월 17일까지 진행한다고 합니다.

Swift Studennt Challenge 관련 안내

대회 우승자?에게는 명예?를 주네요. 부상으로 WWDC20 자켓과 핀셋? 을 준다는데... 학생분들은 참고 바랍니다. 입상하면 애플 정직원 취업이... 가능하면 좋겠다... ^^;;

이번에는 어떤 신기술과 제품들이 우리의 눈과 귀를 즐겁게 해 줄지 벌써부터 기대가 되네요. 저도 실력이 되서 저런 곳에 참석을 해보고 싶네요. =_=;;


WRITTEN BY
비트센스
뷰파인더로 보는 프로그래머의 세상 페이스북 @bitsense 트위터 @picory 스카이프 picory MSN drawhalf@dreamwiz.com

트랙백  0 , 댓글  0개가 달렸습니다.
secret
import psycopg2 as pg2
from psycopg2.extras import DictCursor

conn = pg2.connect()
cursor = conn.cursor(cursor_factory=DictCursor)
cursor.execute('select %s, %s', ('test', 2))
cursor.query
"select E'test', 2"

WRITTEN BY
비트센스
뷰파인더로 보는 프로그래머의 세상 페이스북 @bitsense 트위터 @picory 스카이프 picory MSN drawhalf@dreamwiz.com

트랙백  0 , 댓글  0개가 달렸습니다.
secret

 

// 설치
npm i --save @ckeditor/ckeditor5-vue @ckeditor/ckeditor5-build-classic

// package.json 확인
    "dependencies": {
        "@ckeditor/ckeditor5-build-classic": "^16.0.0",
        "@ckeditor/ckeditor5-vue": "^1.0.1"
    }

// template 파일 샘플
<template>
    <ckeditor :editor="editor"
              v-model="editorData"
              :config="editorConfig"
    />
</template>

<script>
    import ClassicEditor from '@ckeditor/ckeditor5-build-classic'
    import CKEditor from '@ckeditor/ckeditor5-vue'
    
    export default {
        name: 'CKEditor',
        components: {
            ckeditor: CKEditor.component
        },
        data: () => ({
            editor: ClassicEditor,
            editorData: '<p>Content of the editor.</p>',
            editorConfig: {
                // The configuration of the editor.
                height: '500px',
                language: 'ko'
            }
        })
    }
</script>

참조 URL : https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/frameworks/vuejs.html


WRITTEN BY
비트센스
뷰파인더로 보는 프로그래머의 세상 페이스북 @bitsense 트위터 @picory 스카이프 picory MSN drawhalf@dreamwiz.com

트랙백  0 , 댓글  0개가 달렸습니다.
secret