当前位置: 首页 > news >正文

PgAUT插件的原理

在MySQL中,如果一个列的类型是TIMESTAMPDATETIME,并且设置了on update current_timestamp,则该字段的值会随着update命令更新行记录时自动更新成当前时间。这个特性在应用开发中非常有用,方便记录每一行最后的更新时间。

但这个特性不是标准SQL语法,数据库迁移到PostgreSQL后不得不在每条update语句里添加set updated_at = current_timestamp,难免会有遗漏。因此,我开发了PgAUT插件,提供类似MySQL的功能。

一、标记自动更新字段

插件的原理是在创建表的时候,自动创建一个与之相应的trigger,在更新语句执行之前把需要自动更新的字段的值统一赋值为clock_timestamp()

为了能标记出哪些字段是需要自动更新,使用“域”为timestamp类型创建了一个特殊的别名,后续用这个别名作为需要自动更新字段的类型:

create domain auto_update_timestamp as timestamp;

-- 创建表
create table foo (
  id bigint primary key,
  c1 auto_update_timestamp default current_timestamp
);
-- 修改表
alter table foo add column c2 auto_update_timestamp;

二、创建触发器(Trigger)

知道了哪些列需要自动更新后,可以给表绑定一个触发器,在更新的时候自动更新值:

create or replace foo_on_update_handler() returns trigger as 
$$

begin
  new.c1 = clock_timestamp();
  new.c2 = clock_timestamp();
  return new;
end;

$$
 language plpgsql;

create trigger foo_on_update_trigger
 before update on foo
 for each row execute
 procedure foo_on_update_handler();

三、事件触发器(Event Trigger)

有别与普通的触发器,事件触发器能捕捉所有DDL的变动,例如CREATE TABLEALTER TABLEDROP TABLE等。利用这个特性,就能实现在创建表的时候,自动创建与之相应的触发器:

create or replace function table_event_trigger_handler()
 returns event_trigger as 
$$

declare
  _e record;
  _sql text;
  _schema_name text;
  _table_name text;
begin
  -- 通过 pg_event_trigger_ddl_commands 获取当前变更的元素信息
  for _e in select * from pg_event_trigger_ddl_commands() loop
    if _e.object_type = 'table' and _e.command_tag = 'CREATE TABLE' then
      -- 从 pg_event_trigger_ddl_commands 无法拿到表的名称
      -- 只能手工从 pg_class 中获取 schema 和 table 的名称
      select
        pg_namespace.nspname,
        pg_class.relname
      into
        _schema_name,
        _table_name
      from
        pg_class
      inner join
        pg_namespace
      on
        pg_class.relnamespace = pg_namespace.oid
      where
        pg_class.oid = _e.objid;

      -- 从列元信息表中获得变更表的所有类型为 auto_update_timestamp 的列名
      -- 拼装成一组 new.<column-name> := clock_timestamp();
      select
        string_agg('  new.' || column_name || ' := clock_timestamp();', E'\n')
      into
        _sql
      from
        information_schema.columns
      where
        table_schema = _schema_name
        and table_name = _table_name
        and domain_name = 'auto_update_timestamp';

      -- 执行动态语句,创建触发器函数
      execute format($SQL$
create or replace function %s.%s_on_update_handler() returns trigger as $HANDLER$
begin
  %s
  return new;
end;
$HANDLER$ language plpgsql;
$SQL$, _schema_name, _table_name, _sql);

      -- 执行动态语句,创建触发器
      execute format($SQL$
create trigger %s_%s_on_update_trigger
 before update on %s.%s
 for each row execute
 procedure %s.%s_on_update_handler()
$SQL$, _schema_name, _table_name
     , _schema_name, _table_name
     , _schema_name, _table_name);

    end if;
  end loop;
end;

$$
 language plpgsql;

-- 创建事件触发器,处理CREATE TABLE事件
create event trigger table_event_trigger
  on ddl_command_end
  when tag in ('CREATE TABLE')
  execute procedure table_event_trigger_handler();

四、补齐其他事件触发器

上述代码实现了CREATE TABLE时自动创建触发器函数与触发器,并绑定到新建的表上,还需要创建ALTER TABLE处理添加或删除表字段时更新触发器函数,以及DROP TABLE处理时删除触发器函数。

细节可以参考插件的源码:https://github.com/redraiment/pgaut/blob/master/pgaut--1.0.0.sql

相关文章:

  • beetl的内置函数 (如strutil 工具类)
  • JDK命令行(jps、jstat、jinfo、jmap、jhat、jstack、jstatd、hprof)与JConsole
  • Aliyun ECS 重置系统
  • Composite组合模式(结构型模式)
  • SQL基础知识
  • 开放源代码库指南
  • WPF换肤之三:WPF中的WndProc
  • 【转】VUE 爬坑之旅-- 如何对公共JS,CSS进行统一管理,全局调用
  • 各个浏览器之间常见的兼容性问题
  • 为什么需要RPC,而不是简单的HTTP接口
  • 和开源硬件相关的几个词,免费、山寨、创客教育,以及未来 | COSCon'18
  • 2018云计算行业现状及2020年云计算发展趋势
  • 当我们谈论Promise时,我们说些什么
  • 谷歌推迟公布Google+漏洞遭参议员不满
  • 今日头条完成超25亿美元融资 软银GA与KKR参与
  • 【node学习】协程
  • 2019.2.20 c++ 知识梳理
  • Bootstrap JS插件Alert源码分析
  • ES6语法详解(一)
  • Java基本数据类型之Number
  • Linux链接文件
  • mysql常用命令汇总
  • Swift 中的尾递归和蹦床
  • TypeScript迭代器
  • unity如何实现一个固定宽度的orthagraphic相机
  • Vue.js源码(2):初探List Rendering
  • 服务器从安装到部署全过程(二)
  • 基于 Babel 的 npm 包最小化设置
  • 扑朔迷离的属性和特性【彻底弄清】
  • 使用Maven插件构建SpringBoot项目,生成Docker镜像push到DockerHub上
  • 【干货分享】dos命令大全
  • 国内开源镜像站点
  • ​LeetCode解法汇总2182. 构造限制重复的字符串
  • ​虚拟化系列介绍(十)
  • #LLM入门|Prompt#2.3_对查询任务进行分类|意图分析_Classification
  • #NOIP 2014#day.2 T1 无限网络发射器选址
  • (1/2)敏捷实践指南 Agile Practice Guide ([美] Project Management institute 著)
  • (4)(4.6) Triducer
  • (Redis使用系列) Springboot 使用Redis+Session实现Session共享 ,简单的单点登录 五
  • (zt)基于Facebook和Flash平台的应用架构解析
  • (附源码)spring boot火车票售卖系统 毕业设计 211004
  • (九)信息融合方式简介
  • (六)Hibernate的二级缓存
  • (原創) 如何使用ISO C++讀寫BMP圖檔? (C/C++) (Image Processing)
  • (中等) HDU 4370 0 or 1,建模+Dijkstra。
  • (状压dp)uva 10817 Headmaster's Headache
  • .bat批处理(九):替换带有等号=的字符串的子串
  • .bat批处理(十一):替换字符串中包含百分号%的子串
  • .NET Micro Framework初体验(二)
  • .NET Reactor简单使用教程
  • .net web项目 调用webService
  • .NET高级面试指南专题十一【 设计模式介绍,为什么要用设计模式】
  • .NET中两种OCR方式对比
  • .so文件(linux系统)
  • /dev下添加设备节点的方法步骤(通过device_create)