adminpack 提供了大量支持功能,pgAdmin 和其他管理工具可以使用這些功能提供額外功能,例如遠程管理伺服器日誌文件。預設情況下,只有資料庫超級用戶才能使用所有這些功能,但其他用戶也可以使用 GRANT 命令使用這些功能。 我們先來看一下他支持的函數,可以通過 \dx+ adminpack ...
adminpack 提供了大量支持功能,pgAdmin 和其他管理工具可以使用這些功能提供額外功能,例如遠程管理伺服器日誌文件。預設情況下,只有資料庫超級用戶才能使用所有這些功能,但其他用戶也可以使用 GRANT 命令使用這些功能。
我們先來看一下他支持的函數,可以通過 \dx+ adminpack 來進行查看
- function pg_file_rename(text,text) 重命名文件
- function pg_file_rename(text,text,text) 重命名文件,如果新文件存在,將將其命名為第三個參數的名字
- function pg_file_sync(text) 文件刷入磁碟
- function pg_file_unlink(text) 刪除文件
- function pg_file_write(text,text,boolean) 寫文件
- function pg_logdir_ls() 列出日誌目錄下的文件
pg_file_rename(text,text)
用於重命名文件,我們看一下 sql 代碼
CREATE FUNCTION pg_catalog.pg_file_rename(text, text)
RETURNS bool
AS 'SELECT pg_catalog.pg_file_rename($1, $2, NULL::pg_catalog.text);'
LANGUAGE SQL VOLATILE STRICT;
這裡我們看到兩個參數版本的 pg_file_rename
直接調用來三參數版本的 pg_file_rename
, 因此我們直接查看三參數版本的 SQL 代碼
CREATE OR REPLACE FUNCTION pg_catalog.pg_file_rename(text, text, text)
RETURNS bool
AS 'MODULE_PATHNAME', 'pg_file_rename_v1_1'
LANGUAGE C VOLATILE;
這個 SQL 代碼直接調用來 C 函數 pg_file_rename_v1_1
來實現文件重命名。
現在我們來看一下 C 函數 pg_file_rename_v1_1
Datum
pg_file_rename_v1_1(PG_FUNCTION_ARGS)
{
text *file1;
text *file2;
text *file3;
bool result;
if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
PG_RETURN_NULL();
file1 = PG_GETARG_TEXT_PP(0);
file2 = PG_GETARG_TEXT_PP(1);
if (PG_ARGISNULL(2))
file3 = NULL;
else
file3 = PG_GETARG_TEXT_PP(2);
result = pg_file_rename_internal(file1, file2, file3);
PG_RETURN_BOOL(result);
}
這個代碼中僅僅是判斷參數是否為空,如果不為空,則獲取參數,然後調用 pg_file_rename_internal
這個函數
static bool
pg_file_rename_internal(text *file1, text *file2, text *file3)
{
char *fn1,
*fn2,
*fn3;
int rc;
fn1 = convert_and_check_filename(file1);
fn2 = convert_and_check_filename(file2);
if (file3 == NULL)
fn3 = NULL;
else
fn3 = convert_and_check_filename(file3);
if (access(fn1, W_OK) < 0)
{
ereport(WARNING,
(errcode_for_file_access(),
errmsg("file \"%s\" is not accessible: %m", fn1)));
return false;
}
if (fn3 && access(fn2, W_OK) < 0)
{
ereport(WARNING,
(errcode_for_file_access(),
errmsg("file \"%s\" is not accessible: %m", fn2)));
return false;
}
rc = access(fn3 ? fn3 : fn2, W_OK);
if (rc >= 0 || errno != ENOENT)
{
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_FILE),
errmsg("cannot rename to target file \"%s\"",
fn3 ? fn3 : fn2)));
}
if (fn3)
{
if (rename(fn2, fn3) != 0)
{
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not rename \"%s\" to \"%s\": %m",
fn2, fn3)));
}
if (rename(fn1, fn2) != 0)
{
ereport(WARNING,
(errcode_for_file_access(),
errmsg("could not rename \"%s\" to \"%s\": %m",
fn1, fn2)));
if (rename(fn3, fn2) != 0)
{
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not rename \"%s\" back to \"%s\": %m",
fn3, fn2)));
}
else
{
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FILE),
errmsg("renaming \"%s\" to \"%s\" was reverted",
fn2, fn3)));
}
}
}
else if (rename(fn1, fn2) != 0)
{
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not rename \"%s\" to \"%s\": %m", fn1, fn2)));
}
return true;
}
這個函數的整體邏輯為,先將 text* 類型的數據轉換為 char* 類型的數據,會在這個轉換的過程中處理一些路徑相關和許可權驗證的問題。
然後先判斷一下 fn1 是不是存在,如果不存在那肯定是沒法重命名的,然後判斷一下 fn3 是不是為空,並且 fn2 是不是存在,如果不存在,那麼將 fn2 重命名為 fn3 也會失敗。
然後判斷一下 fn3 是不是存在,如果存在,那麼說明文件已經存在,肯定不能重命名,也會報一個 DUPLICATE 的錯誤。如果 fn3 為空,那麼就判斷 fn2 文件是不是存在,如果存在那也是不能重命名的。
接下來,我們就可以將 fn2 重命名成 fn3, 然後將 fn1 重命名為 fn2,
如果 fn1 重命名為 fn2 出錯,則將 fn3 重名名為 fn2 ,即撤消之前的修改操作。
那如果 fn3 為空,直接將 fn1 重命名為 fn2 就可以了,
pg_file_sync
我們先來看一下 SQL 代碼:
CREATE OR REPLACE FUNCTION pg_catalog.pg_file_sync(text)
RETURNS void
AS 'MODULE_PATHNAME', 'pg_file_sync'
LANGUAGE C VOLATILE STRICT;
可以看到他調用的是 C 函數 pg_file_sync
,我們來看一下這個 C 代碼:
Datum
pg_file_sync(PG_FUNCTION_ARGS)
{
char *filename;
struct stat fst;
filename = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
if (stat(filename, &fst) < 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not stat file \"%s\": %m", filename)));
fsync_fname_ext(filename, S_ISDIR(fst.st_mode), false, ERROR);
PG_RETURN_VOID();
}
可以看到這個僅僅是將 text 類型的數據轉換為 char * 類型的數據。然後調用 storage 的函數實現的功能。(src/backend/storage/file/fd.c)
pg_file_unlink
我們先來看一下 SQL 代碼:
CREATE OR REPLACE FUNCTION pg_catalog.pg_file_unlink(text)
RETURNS bool
AS 'MODULE_PATHNAME', 'pg_file_unlink_v1_1'
LANGUAGE C VOLATILE STRICT;
發現它調用了 C 函數 pg_file_unlink_v1_1
. 我們來看一下這個函數:
Datum
pg_file_unlink_v1_1(PG_FUNCTION_ARGS)
{
char *filename;
filename = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
if (access(filename, W_OK) < 0)
{
if (errno == ENOENT)
PG_RETURN_BOOL(false);
else
ereport(ERROR,
(errcode_for_file_access(),
errmsg("file \"%s\" is not accessible: %m", filename)));
}
if (unlink(filename) < 0)
{
ereport(WARNING,
(errcode_for_file_access(),
errmsg("could not unlink file \"%s\": %m", filename)));
PG_RETURN_BOOL(false);
}
PG_RETURN_BOOL(true);
}
這個函數的整體邏輯是將 text* 類型的數據轉換為 char* 類型的數據,並處理路徑相關的問題,然後判斷一下文件是不是可訪問的。然後調用 unlink
對文件進行刪除。
pg_file_write
看一下 SQL 代碼:
CREATE OR REPLACE FUNCTION pg_catalog.pg_file_write(text, text, bool)
RETURNS bigint
AS 'MODULE_PATHNAME', 'pg_file_write_v1_1'
LANGUAGE C VOLATILE STRICT;
發現它調用的是 C 函數 pg_file_write_v1_1
Datum
pg_file_write_v1_1(PG_FUNCTION_ARGS)
{
text *file = PG_GETARG_TEXT_PP(0);
text *data = PG_GETARG_TEXT_PP(1);
bool replace = PG_GETARG_BOOL(2);
int64 count = 0;
count = pg_file_write_internal(file, data, replace);
PG_RETURN_INT64(count);
}
/* ------------------------------------
* pg_file_write_internal - Workhorse for pg_file_write functions.
*
* This handles the actual work for pg_file_write.
*/
static int64
pg_file_write_internal(text *file, text *data, bool replace)
{
FILE *f;
char *filename;
int64 count = 0;
filename = convert_and_check_filename(file);
if (!replace)
{
struct stat fst;
if (stat(filename, &fst) >= 0)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_FILE),
errmsg("file \"%s\" exists", filename)));
f = AllocateFile(filename, "wb");
}
else
f = AllocateFile(filename, "ab");
if (!f)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not open file \"%s\" for writing: %m",
filename)));
count = fwrite(VARDATA_ANY(data), 1, VARSIZE_ANY_EXHDR(data), f);
if (count != VARSIZE_ANY_EXHDR(data) || FreeFile(f))
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not write file \"%s\": %m", filename)));
return (count);
}
我們可以看到 pg_file_write_v1_1
僅僅是獲取了參數,然後就調用了 pg_file_write_internal
函數。
這個函數的主要邏輯是將 text* 的數據轉換為 char* 的數據 。然後判斷一下 replace
參數,如果為 false,則文件不能存在,然後使用 AllocateFile
創建一個文件。
然後使用 fwrite
將數據寫入文件,VARDATA_ANY
巨集的作用是獲取實際的數據指針,VARSIZE_ANY_EXHDR
的作用是獲取數據的長度。最後返回寫入的長度。
pg_logdir_ls
我們看一下 SQL 代碼:
CREATE OR REPLACE FUNCTION pg_catalog.pg_logdir_ls()
RETURNS setof record
AS 'MODULE_PATHNAME', 'pg_logdir_ls_v1_1'
LANGUAGE C VOLATILE STRICT;
這裡它調用了 C 函數 pg_logidr_ls_v1_1
,我們看一下這個 C 函數:
Datum
pg_logdir_ls_v1_1(PG_FUNCTION_ARGS)
{
return (pg_logdir_ls_internal(fcinfo));
}
static Datum
pg_logdir_ls_internal(FunctionCallInfo fcinfo)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool randomAccess;
TupleDesc tupdesc;
Tuplestorestate *tupstore;
AttInMetadata *attinmeta;
DIR *dirdesc;
struct dirent *de;
MemoryContext oldcontext;
if (strcmp(Log_filename, "postgresql-%Y-%m-%d_%H%M%S.log") != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("the log_filename parameter must equal 'postgresql-%%Y-%%m-%%d_%%H%%M%%S.log'")));
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("set-valued function called in context that cannot accept a set")));
if (!(rsinfo->allowedModes & SFRM_Materialize))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("materialize mode required, but it is not allowed in this context")));
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
tupdesc = CreateTemplateTupleDesc(2);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "starttime",
TIMESTAMPOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "filename",
TEXTOID, -1, 0);
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
rsinfo->returnMode = SFRM_Materialize;
rsinfo->setResult = tupstore;
rsinfo->setDesc = tupdesc;
MemoryContextSwitchTo(oldcontext);
attinmeta = TupleDescGetAttInMetadata(tupdesc);
dirdesc = AllocateDir(Log_directory);
while ((de = ReadDir(dirdesc, Log_directory)) != NULL)
{
char *values[2];
HeapTuple tuple;
char timestampbuf[32];
char *field[MAXDATEFIELDS];
char lowstr[MAXDATELEN + 1];
int dtype;
int nf,
ftype[MAXDATEFIELDS];
fsec_t fsec;
int tz = 0;
struct pg_tm date;
/*
* Default format: postgresql-YYYY-MM-DD_HHMMSS.log
*/
if (strlen(de->d_name) != 32
|| strncmp(de->d_name, "postgresql-", 11) != 0
|| de->d_name[21] != '_'
|| strcmp(de->d_name + 28, ".log") != 0)
continue;
/* extract timestamp portion of filename */
strcpy(timestampbuf, de->d_name + 11);
timestampbuf[17] = '\0';
/* parse and decode expected timestamp to verify it's OK format */
if (ParseDateTime(timestampbuf, lowstr, MAXDATELEN, field, ftype, MAXDATEFIELDS, &nf))
continue;
if (DecodeDateTime(field, ftype, nf, &dtype, &date, &fsec, &tz))
continue;
/* Seems the timestamp is OK; prepare and return tuple */
values[0] = timestampbuf;
values[1] = psprintf("%s/%s", Log_directory, de->d_name);
tuple = BuildTupleFromCStrings(attinmeta, values);
tuplestore_puttuple(tupstore, tuple);
}
FreeDir(dirdesc);
return (Datum) 0;
}
這裡它直接調用了 pg_logdir_ls_internal
函數,其中的 fcinfo
是這個巨集展開的:
#define PG_FUNCTION_ARGS FunctionCallInfo fcinfo
我們來看一下 pg_logdir_ls_internal
函數:
首先我們看到了它從 fcinfo
中獲取了一個 resultinfo
的欄位,然後將其轉換為 ReturnSetInfo
這個結構體指針。我們來看一下這個結構體
/*
* When calling a function that might return a set (multiple rows),
* a node of this type is passed as fcinfo->resultinfo to allow
* return status to be passed back. A function returning set should
* raise an error if no such resultinfo is provided.
*/
typedef struct ReturnSetInfo
{
NodeTag type;
/* values set by caller: */
ExprContext *econtext; /* context function is being called in */
TupleDesc expectedDesc; /* tuple descriptor expected by caller */
int allowedModes; /* bitmask: return modes caller can handle */
/* result status from function (but pre-initialized by caller): */
SetFunctionReturnMode returnMode; /* actual return mode */
ExprDoneCond isDone; /* status for ValuePerCall mode */
/* fields filled by function in Materialize return mode: */
Tuplestorestate *setResult; /* holds the complete returned tuple set */
TupleDesc setDesc; /* actual descriptor for returned tuples */
} ReturnSetInfo;
從這個註釋中我們可以看到,當我們調用的函數需要返回一個集合,即多行數據的時候,我們就可以使用這個結構體,而 resultinfo
的實際類型是一個 fmNodePtr
,實際上就是一個 Node 節點。而 ReturnSetInfo
也是一個 Node 節點。
接下來我們定義來一些用於返回結果需要的輔助數據結構。
第一個判斷我們的 Log_filename
的格式是不是符合 postgresql-%Y-%m-%d_%H%M%S.log
這個規則,不符合這個規則是無法使用這個函數的。Log_filename
是一個 GUC 參數,你可以在 postgresql.conf
中修改,也可以使用 show log_filename
命令來查看當前的值。
然後我們檢查一下 rsinfo
變數的類型是不是一個 ReturnSetInfo
,實際上就是通過 NodeTag 進行比較的。
接下來我們檢查一下 rsinfo
中的 allowedMode
返回模式是否是 SFRM_Materialize
這個模式可以讓我們將要返回的數據都存儲在 Tuplestore
中。
接下來我們需要將記憶體上下文切換到 rsinfo->econtext->ecxt_per_query_memory
,在這個記憶體上下文中我們保存要返回的結果,即這裡的 tuple.
然後我們使用 CreateTemplateTupleDesc
來創建一個 tuple 的描述,其實我感覺可以理解為 DDL ,即這個表各個欄位是什麼樣的數據。這裡我們添加了兩個欄位,其數據類型分別為 TIMESTAMP
和 TEXT
.
然後我們使用 tuplestore_begin_heap
函數創建一個 tuplestore
,我們最終的結果也將存放在這個數據結構中。
attinmeta = TupleDescGetAttInMetadata(tupdesc);
這個語句把 tuple 的屬性信息存儲到 attinmeta
這個會在我們後續構造 tuple 的時候要用到。
接下來,我們打開目錄,這個 Log_directory
也是一個 GUC 參數可以設置和查看。
這個目錄是相對於 data 目錄來說的。
接下來,我們遍歷目錄中的文件,查看文件的名字是否滿足 postgresql-
這種形式,然後將文件名中包含的日期信息提取出來,然後將其變成好看的日期格式,最後我們將日期和文件名寫入到一個 tuple 里
values[0] = timestampbuf;
values[1] = psprintf("%s/%s", Log_directory, de->d_name);
tuple = BuildTupleFromCStrings(attinmeta, values);
其中的 attinmeta
就是我們之前準備好的 tuple 描述信息,構造好一個 tuple 以後,我們就可以把它放在 tuplestore 裡邊了。
tuplestore_puttuple(tupstore, tuple);
最後關閉目錄。