做個奇怪的東西-Golang快速實現功能、c封裝成PostgreSQL的擴展
前言
以前寫過很多數據庫方面的存儲過程之類的,應該算是小型公司的一種全棧要求,什么都要會,最多的話一個系統寫了一兩百個函數或存儲過程。最近因為在改系統,想起來以前的區號和工作時間等,都是純sql或者在開發語言層面干掉了,那么不如寫成PostgreSQL的function,本來應該這樣,但是由于以前的改動大,要多棧真的腦子變來變去,就不了了之。
操作
首先看下以下兩個表結構:
用途是將基本的手機號段和區號以及省市信息記錄到nway_base_mobile_location表中;在nway_area_plan中,定義這條規則所要使用到的區號列表,用于和nway_base_mobile_location中的district_no匹配。
CREATE TABLE IF NOT EXISTS public.nway_base_mobile_location
(
no character varying(7) COLLATE pg_catalog."default" NOT NULL,
location character varying(50) COLLATE pg_catalog."default",
district_no character varying(100) COLLATE pg_catalog."default"
)
WITH (
OIDS = FALSE
)
TABLESPACE pg_default;
ALTER TABLE IF EXISTS public.nway_base_mobile_location
OWNER to postgres;
COMMENT ON TABLE public.nway_base_mobile_location
IS '上海寧衛信息技術有限公司,李浩。手機歸屬地';
COMMENT ON COLUMN public.nway_base_mobile_location.no
IS '手機號段';
COMMENT ON COLUMN public.nway_base_mobile_location.location
IS '所在地';
COMMENT ON COLUMN public.nway_base_mobile_location.district_no
IS '區號';
-- Index: PK_NO_BASE_MOBILE_LOCATION
-- DROP INDEX IF EXISTS public."PK_NO_BASE_MOBILE_LOCATION";
CREATE INDEX IF NOT EXISTS "PK_NO_BASE_MOBILE_LOCATION"
ON public.nway_base_mobile_location USING btree
(no COLLATE pg_catalog."C" varchar_ops ASC NULLS LAST)
TABLESPACE pg_default;
-- Index: base_mobile_location_district_no
-- DROP INDEX IF EXISTS public.base_mobile_location_district_no;
CREATE INDEX IF NOT EXISTS base_mobile_location_district_no
ON public.nway_base_mobile_location USING btree
(district_no COLLATE pg_catalog."default" ASC NULLS LAST)
TABLESPACE pg_default;
-- Index: base_mobile_location_no
-- DROP INDEX IF EXISTS public.base_mobile_location_no;
CREATE INDEX IF NOT EXISTS base_mobile_location_no
ON public.nway_base_mobile_location USING btree
(no COLLATE pg_catalog."default" ASC NULLS LAST)
TABLESPACE pg_default;
------------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS public.nway_area_plan
(
id bigint NOT NULL DEFAULT nextval('nway_area_plan_id_seq'::regclass),
plan_name character varying(100) COLLATE pg_catalog."default" NOT NULL DEFAULT ''::character varying,
plan_desc text COLLATE pg_catalog."default",
node_id bigint,
district_no text COLLATE pg_catalog."default" DEFAULT ''::text,
CONSTRAINT nway_area_plan_pkey PRIMARY KEY (id)
)
WITH (
OIDS = FALSE
)
TABLESPACE pg_default;
ALTER TABLE IF EXISTS public.nway_area_plan
OWNER to postgres;
COMMENT ON TABLE public.nway_area_plan
IS '區域策略';
COMMENT ON COLUMN public.nway_area_plan.district_no
IS '電話區號列表';
以前的做法是使用Python或golang寫個獨立的應用,進行把xls等手機號段表單獨運行導入,不過前幾天看到 https://zhmin.github.io/ 上的一篇《Postgresql 編寫自定義 C 函數》,于是就想是不是寫成一個PostgreSQL擴展會好玩些,但是面臨的問題也比較現實:1. c語言的運算部分,代碼寫起來費勁;2. c語言在各類擴展中質量參差不齊,容易陷入調試死胡同;3. c語言各類應用的庫都不如GO、python等相對完備。所以采用Go開發成動靜態庫,再用c簡單調用一層,生成對應的PostgreSQL擴展動態庫(由Go語言直接生成也可以,不過那要折騰,還不如直接c包一層)。
代碼
將開發的Go的庫通過
go build --ldflags "-s -w" -buildmode=c-shared -o libImptFile.so main.go
生成so及頭文件
//libImptFile.h
//有一堆的定義等略過
#ifdef __cplusplus
extern "C" {
#endif
//入參
//dbstring:數據庫連接字符串
//file_path:本地的xsls或xls等excel表
//no_idx:號段字段在excel表格中的第幾列
//location_idx:位置字段在excel表格中的第幾列
//district_no_idx:區號字段在excel表格中的第幾列
//err_msg:錯誤時返回的字符串說明,不超過256長度
//返回值
//整型:0完成;-1文件不存在;-2文件格式讀取不出;1寫入庫中時異常
extern int ImportMobLocFromFile(GoString dbstring, GoString file_path, GoInt no_idx, GoInt location_idx, GoInt district_no_idx, char* err_msg);
#ifdef __cplusplus
}
#endif
然后再用c來調用
#include "libImptFile.h"
#include "postgres.h"
#include "fmgr.h"
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(impt_file_func);
Datum impt_file_func(PG_FUNCTION_ARGS)
{
char* dbstr=PG_GETARG_CSTRING(0);
char* fp = PG_GETARG_CSTRING(1);
int32 n = PG_GETARG_INT32(2);
int32 l = PG_GETARG_INT32(3);
int32 d = PG_GETARG_INT32(4);
char* err_msg=(char*) palloc(256);
//需要加個套,Gostring和GoInt
int32 r=0;
GoString dbstring={dbstr,strlen(dbstr)};
GoString file_path={fp,strlen(fp)};
GoInt no_idx=(GoInt)n;
GoInt location_idx = (GoInt)l;
GoInt district_no_idx=(GoInt)d;
r = ImportMobLocFromFile(dbstring,file_path,no_idx,location_idx,district_no_idx,err_msg);
if (r!=0){
printf("call ImportMobLocFromFile failed:%s",err_msg );
}
pfree(err_msg);
PG_RETURN_INT32(r);
}
再然后編譯后調用
gcc -fPIC -c impt_file_func.c -I/usr/include/pgsql/server/ -shared -o impt_file_func.so
接著在psql中
CREATE FUNCTION impt_file_func(TEXT,TEXT,integer,integer,integer) RETURNS integer
AS '/var/lib/11/pgsql/impt_file_func.so', 'impt_file_func' LANGUAGE C STRICT;
OK,那么就去調用吧
select impt_file_func("user=postgres dbname=cloudcc password=Nway2017 host=127.0.0.1 port=5432 sslmode=disable","/home/20221011.xlsx",0,1,2) as nway_result;