跟我一塊兒讀postgresql源碼(一)——psql命令

進公司以來作的都是postgresql相關的東西,每次都是測試、修改邊邊角角的東西,這樣感受只能留在表面,不能深刻了解這個開源數據庫的精髓,遂想着看看postgresql的源碼,以加深對數據庫的理解,也算是好好提升本身。前端

可是目標很性感,現實很殘酷,postgesql的源碼都已經百萬級了。單單.c文件都有1000+。怎麼辦,硬着頭皮看吧,所幸postgrsql的源碼很規範,這應該會給我省很多事。給本身頂一個小目標:天天看一點源碼,天天都更新作不到,每週都更新吧,每週至少一篇。但願看到個人博客的朋友們也和我一塊兒學習,我有什麼理解不對的地方,也但願你們提出意見~linux

大部分人初次接觸postgresql通常都是接觸psql這個命令行工具吧,那麼咱們今天就從psql程序的源碼開始看吧。sql

對了,這裏要說一下,我看的代碼指的是postgresql9.5.4這個版本,不一樣版本的代碼固然是有區別的~shell

psql的源碼分爲兩部分,一部分是psql的前臺處理代碼,代碼都放在/src/bin/psql下;另外一部分就是後臺的查詢處理過程的代碼,代碼較多,過程也較爲複雜。這部分代碼分佈在/src/backend/目錄下的許多子目錄中。這篇博客是試水的文章,就先看看前臺的代碼吧。後臺的代碼放在後面的博客(若是有的話~)裏再細細的說吧。數據庫

讓咱們先打開/src/bin/psql目錄,這下面放的就是psql的前端程序代碼。基本全部的程序都有個main函數,psql的main函數就放在startup.c裏面。windows

咱們先看兩個數據結構:後端

enum _actions
{
    ACT_NOTHING = 0,
    ACT_SINGLE_SLASH,
    ACT_LIST_DB,
    ACT_SINGLE_QUERY,
    ACT_FILE
};

struct adhoc_opts
{
    char       *dbname;
    char       *host;
    char       *port;
    char       *username;
    char       *logfilename;
    enum _actions action;
    char       *action_string;
    bool        no_readline;
    bool        no_psqlrc;
    bool        single_txn;
};

其中:session

枚舉類型**_actions**表明psql命令行程序當前所處的狀態;數據結構

結構體adhoc_opts 儲存了當前命令行程序的一些登陸信息,好比登錄的數據庫、主機、端口和日誌文件的位置等等。ide

一樣還有幾個小函數:

static void parse_psql_options(int argc, char *argv[],   //解析命令行選項
                   struct adhoc_opts * options);         
static void process_psqlrc(char *argv0);                 //載入.psqlrc文件,若是存在的話
static void process_psqlrc_file(char *filename);         //被process_psqlrc()調用
static void showVersion(void);                           //格式化輸出PostgreSQL的版本
static void EstablishVariableSpace(void);                //

對了,開頭的兩個宏定義指明瞭對psql命令行窗口的定製化信息:

#ifndef WIN32
#define SYSPSQLRC    "psqlrc"
#define PSQLRC      ".psqlrc"
#else
#define SYSPSQLRC   "psqlrc"
#define PSQLRC      "psqlrc.conf"
#endif

經過這兩個文件能夠定製本身的命令行窗口(分別指linux和windows下)的信息顯示式樣,很方便實用。

不廢話,進main函數。
第一個if顯示的很顯然是psql的help和version命令。

在後面有一個變量很重要:pset。它的數據結構PsqlSettings的定義放在src/bin/psql/settings.h裏面。這個數據結構主要要表達的是當前psql命令行屬性和狀態集,經過這些屬性和狀態集判斷和處理來控制程序的走向。

typedef struct _psqlSettings
{
    PGconn     *db;             /* connection to backend */
    int         encoding;       /* client_encoding */
    FILE       *queryFout;      /* where to send the query results */
    bool        queryFoutPipe;  /* queryFout is from a popen() */
    FILE       *copyStream;     /* Stream to read/write for \copy command */
    printQueryOpt popt;
    char       *gfname;         /* one-shot file output argument for \g */
    char       *gset_prefix;    /* one-shot prefix argument for \gset */
    bool        notty;          /* stdin or stdout is not a tty (as determined on startup) */                            
    enum trivalue getPassword;  /* prompt the user for a username and password */
    FILE       *cur_cmd_source; /* describe the status of the current main  loop */                             
    bool        cur_cmd_interactive;
    int         sversion;       /* backend server version */
    const char *progname;       /* in case you renamed psql */
    char       *inputfile;      /* file being currently processed, if any */
    uint64      lineno;         /* also for error reporting */
    uint64      stmt_lineno;    /* line number inside the current statement */
    bool        timing;         /* enable timing of all queries */
    FILE       *logfile;        /* session log file handle */
    VariableSpace vars;         /* "shell variable" repository */

    /*
     * The remaining fields are set by assign hooks associated with entries in
     * "vars".  They should not be set directly except by those hook
     * functions.
     */
    bool        autocommit;
    bool        on_error_stop;
    bool        quiet;
    bool        singleline;
    bool        singlestep;
    int         fetch_count;
    PSQL_ECHO   echo;
    PSQL_ECHO_HIDDEN echo_hidden;
    PSQL_ERROR_ROLLBACK on_error_rollback;
    PSQL_COMP_CASE comp_case;
    HistControl histcontrol;
    const char *prompt1;
    const char *prompt2;
    const char *prompt3;
    PGVerbosity verbosity;      /* current error verbosity level */
} PsqlSettings;

main主要乾了哪些事兒呢?:好比你輸入:

psql -U postgres -p 26500 -w

程序在讀取psql後面這一大串參數以前,先初始化一些環境變量,當肯定不是--version和--help這種輸出幫助信息就結束的參數時,利用輸入的參數,初始化前面提到的_psqlSettings類型的變量pset。而後驗證登陸密碼(若是指定了的話),

進入334行的MainLoop函數。這個函數的定義在統計文件夾的mainloop.c文件中。這個函數的主要成分就是一個大的循環:

循環讀取命令行的查詢請求-->將請求發日後端-->從後端獲取請求的結果。

值得一說的是,MainLoop維護PQExpBuffer類型的query_buf,previous_buf,history_buf三個buffer。這三個buffer的定義在src/interfaces/libpq/pqexpbuffer.h。定義以下:

typedef struct PQExpBufferData
{
    char       *data;
    size_t      len;
    size_t      maxlen;
} PQExpBufferData;

typedef PQExpBufferData *PQExpBuffer;

其中history_buf保存的是之前的歷史操做。previous_buf 保存的當前操做,因爲psql中每一個命令能夠有多行(經過」」+」Enter」進行分割),因此previous_buf 會一行一行的添加進char* line中的輸入,當一個命令知足發出條件時,再把previous_buf中的數據送到query_buf中去。

在獲取到sql命令後,首先使用函數psql_scan_setup對啓動對指定行的詞法分析。而後調用psql_scan函數返回語句的狀態。返回的狀態有下面幾種:

PSCAN_SEMICOLON     以分號結束的命令
PSCAN_BACKSLASH     以反斜杆結束的命令
PSCAN_INCOMPLETE    到達行尾並無完成的命令
PSCAN_EOL           遇到了EOL結束符

MainLoop函數就是根據返回值控制Buffer,當一個命令輸入完畢之後發送到後臺去執行。

在完成詞法分析後,調用SendQuery(const char *query)函數執行命令,該函數處理沒有鏈接數據庫、事務處理等具體細節。再調用results = PQexec(pset.db, query),獲取數據庫後臺返回的結果。在命令執行之後,使用ProcessCopyResult(results)把運行結果顯示在屏幕上。

若是後臺完成查詢任務,會通知前端它已經空閒,這時前端能夠發送新的查詢命令。下面給出了backend返回給前端的數據結構,前端按照該結構顯示結果。值得說一句的是,雖然對於psql交互窗口顯示出的結果能夠徹底看做是一串字符串,並不須要區分出表中結果每個域。但psql和backend的通訊協議是全部前臺(包括基於GUI界面)和後臺的通訊協議。只不過psql顯示時把它轉換成字符串的表現形式。

struct pg_result
{
    int         ntups;
    int         numAttributes;
    PGresAttDesc *attDescs;
    PGresAttValue **tuples;     /* each PGresTuple is an array of
                                 * PGresAttValue's */
    int         tupArrSize;     /* allocated size of tuples array */
    int         numParameters;
    PGresParamDesc *paramDescs;
    ExecStatusType resultStatus;
    char        cmdStatus[CMDSTATUS_LEN];       /* cmd status from the query */
    int         binary;         /* binary tuple values if binary == 1,
                                 * otherwise text */
    /*
     * These fields are copied from the originating PGconn, so that operations
     * on the PGresult don't have to reference the PGconn.
     */
    PGNoticeHooks noticeHooks;
    PGEvent    *events;
    int         nEvents;
    int         client_encoding;    /* encoding id */
    /*
     * Error information (all NULL if not an error result).  errMsg is the
     * "overall" error message returned by PQresultErrorMessage.  If we have
     * per-field info then it is stored in a linked list.
     */
    char       *errMsg;         /* error message, or NULL if no error */
    PGMessageField *errFields;  /* message broken into fields */
    /* All NULL attributes in the query result point to this null string */
    char        null_field[1];
    /*
     * Space management information.  Note that attDescs and error stuff, if
     * not null, point into allocated blocks.  But tuples points to a
     * separately malloc'd block, so that we can realloc it.
     */
    PGresult_data *curBlock;    /* most recently allocated block */
    int         curOffset;      /* start offset of free space in block */
    int         spaceLeft;      /* number of free bytes remaining in block */
};

該數據結構定義在src/interfaces/libpq/libpq-int.h中。

總之呢,前臺就是這個樣子,和後臺的查詢處理的邏輯相比要簡單得多。

第一次寫對源碼解析的東西,思路比較亂,寫的也比較雜亂無章,基本是想到哪寫到哪。看來之後還得進一步增強寫做的鍛鍊。此次感受好像源碼貼的有點多,下次儘可能注意哈。此次就先這樣了,無論怎麼說總算開了個頭,明天早起在修改修改吧。

但願本身能堅持下去。

相關文章
相關標籤/搜索