Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
Greenplum
Gpdb
提交
af5c86e2
G
Gpdb
项目概览
Greenplum
/
Gpdb
通知
7
Star
1
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
G
Gpdb
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
af5c86e2
编写于
9月 24, 1997
作者:
V
Vadim B. Mikheev
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
General trigger functions for time-travel
上级
f0b9b215
变更
5
隐藏空白更改
内联
并排
Showing
5 changed file
with
525 addition
and
6 deletion
+525
-6
contrib/spi/Makefile
contrib/spi/Makefile
+2
-3
contrib/spi/README
contrib/spi/README
+70
-3
contrib/spi/timetravel.c
contrib/spi/timetravel.c
+372
-0
contrib/spi/timetravel.example
contrib/spi/timetravel.example
+63
-0
contrib/spi/timetravel.source
contrib/spi/timetravel.source
+18
-0
未找到文件。
contrib/spi/Makefile
浏览文件 @
af5c86e2
...
...
@@ -9,12 +9,11 @@ ifdef REFINT_VERBOSE
CFLAGS
+=
-DREFINT_VERBOSE
endif
TARGETS
=
refint
$(DLSUFFIX)
refint.sql
TARGETS
=
refint
$(DLSUFFIX)
refint.sql
timetravel
$(DLSUFFIX)
timetravel.sql
CLEANFILES
+=
$(TARGETS)
all
::
$(TARGETS)
rm
-f
*
.obj
*
.pic
%.sql
:
%.source
rm
-f
$@
;
\
...
...
@@ -23,4 +22,4 @@ all:: $(TARGETS)
-e
"s:_DLSUFFIX_:
$(DLSUFFIX)
:g"
<
$<
>
$@
clean
:
rm
-f
$(TARGETS)
*
.[op]
*
rm
-f
$(TARGETS)
contrib/spi/README
浏览文件 @
af5c86e2
...
...
@@ -28,10 +28,77 @@ being deleted to null), triggered table column names which correspond
to primary/unique key, referencing table name and column names corresponding
to foreign key (, ... - as many referencing tables/keys as specified
by first argument).
Note, that NOT NULL constraint and unique index have to be defined
by
youself.
Note, that NOT NULL constraint and unique index have to be defined
by
youself.
There are examples in refint.example and regression tests
There are examples in refint.example and regression tests
(sql/triggers.sql).
To CREATE FUNCTIONs use refint.sql (will be made by gmake from
refint.source).
2. timetravel.c - functions for implementing time travel feature.
Old internally supported time-travel (TT) used insert/delete
transaction commit times. To get the same feature using triggers
you are to add to a table two columns of abstime type to store
date when a tuple was inserted (start_date) and changed/deleted
(stop_date):
CREATE TABLE XXX (
... ...
date_on abstime default currabstime(),
date_off abstime default 'infinity'
... ...
);
- so, tuples being inserted with NULLs in date_on/date_off will get
_current_date_ in date_on (name of start_date column in XXX) and INFINITY in
date_off (name of stop_date column in XXX).
Tuples with stop_date equal INFINITY are "valid now": when trigger will
be fired for UPDATE/DELETE of a tuple with stop_date NOT equal INFINITY then
this tuple will not be changed/deleted!
If stop_date equal INFINITY then on
UPDATE: only stop_date in tuple being updated will be changed to current
date and new tuple with new data (coming from SET ... in UPDATE) will be
inserted. Start_date in this new tuple will be setted to current date and
stop_date - to INFINITY.
DELETE: new tuple will be inserted with stop_date setted to current date
(and with the same data in other columns as in tuple being deleted).
NOTE:
1. To get tuples "valid now" you are to add _stop_date_ = 'infinity'
to WHERE. Internally supported TT allowed to avoid this...
Fixed rewriting RULEs could help here...
As work arround you may use VIEWs...
2. You can't change start/stop date columns with UPDATE!
Use set_timetravel (below) if you need in this.
FUNCTIONs:
timetravel() is general trigger function.
You are to create trigger BEFORE (!!!) UPDATE OR DELETE using this
function on a time-traveled table. You are to specify two arguments: name of
start_date column and name of stop_date column in triggered table.
currabstime() may be used in DEFAULT for start_date column to get
current date.
set_timetravel() allows you turn time-travel ON/OFF for a table:
set_timetravel('XXX', 1) will turn TT ON for table XXX (and report
old status).
set_timetravel('XXX', 0) will turn TT OFF for table XXX (-"-).
Turning TT OFF allows you do with a table ALL what you want.
There is example in timetravel.example.
To CREATE FUNCTIONs use timetravel.sql (will be made by gmake from
timetravel.source).
contrib/spi/timetravel.c
0 → 100644
浏览文件 @
af5c86e2
/*
* timetravel.c -- function to get time travel feature
* using general triggers.
*/
#include "executor/spi.h"
/* this is what you need to work with SPI */
#include "commands/trigger.h"
/* -"- and triggers */
#include <ctype.h>
/* tolower () */
#define ABSTIMEOID 702
/* it should be in pg_type.h */
AbsoluteTime
currabstime
(
void
);
HeapTuple
timetravel
(
void
);
int32
set_timetravel
(
Name
relname
,
int32
on
);
typedef
struct
{
char
*
ident
;
void
*
splan
;
}
EPlan
;
static
EPlan
*
Plans
=
NULL
;
/* for UPDATE/DELETE */
static
int
nPlans
=
0
;
static
char
**
TTOff
=
NULL
;
static
int
nTTOff
=
0
;
static
EPlan
*
find_plan
(
char
*
ident
,
EPlan
**
eplan
,
int
*
nplans
);
/*
* timetravel () --
* 1. IF an update affects tuple with stop_date eq INFINITY
* then form (and return) new tuple with stop_date eq current date
* and all other column values as in old tuple, and insert tuple
* with new data and start_date eq current date and
* stop_date eq INFINITY
* ELSE - skip updation of tuple.
* 2. IF an delete affects tuple with stop_date eq INFINITY
* then insert the same tuple with stop_date eq current date
* ELSE - skip deletion of tuple.
*
* In CREATE TRIGGER you are to specify start_date and stop_date column
* names:
* EXECUTE PROCEDURE
* timetravel ('date_on', 'date_off').
*/
HeapTuple
/* have to return HeapTuple to Executor */
timetravel
()
{
Trigger
*
trigger
;
/* to get trigger name */
char
**
args
;
/* arguments */
int
attnum
[
2
];
/* fnumbers of start/stop columns */
Datum
oldon
,
oldoff
;
Datum
newon
,
newoff
;
Datum
*
cvals
;
/* column values */
char
*
cnulls
;
/* column nulls */
char
*
relname
;
/* triggered relation name */
Relation
rel
;
/* triggered relation */
HeapTuple
trigtuple
;
HeapTuple
newtuple
=
NULL
;
HeapTuple
rettuple
;
TupleDesc
tupdesc
;
/* tuple description */
int
natts
;
/* # of attributes */
EPlan
*
plan
;
/* prepared plan */
char
ident
[
2
*
NAMEDATALEN
];
bool
isnull
;
/* to know is some column NULL or not */
int
ret
;
int
i
;
/*
* Some checks first...
*/
/* Called by trigger manager ? */
if
(
!
CurrentTriggerData
)
elog
(
WARN
,
"timetravel: triggers are not initialized"
);
/* Should be called for ROW trigger */
if
(
TRIGGER_FIRED_FOR_STATEMENT
(
CurrentTriggerData
->
tg_event
))
elog
(
WARN
,
"timetravel: can't process STATEMENT events"
);
/* Should be called BEFORE */
if
(
TRIGGER_FIRED_AFTER
(
CurrentTriggerData
->
tg_event
))
elog
(
WARN
,
"timetravel: must be fired before event"
);
/* INSERT ? */
if
(
TRIGGER_FIRED_BY_INSERT
(
CurrentTriggerData
->
tg_event
))
elog
(
WARN
,
"timetravel: can't process INSERT event"
);
if
(
TRIGGER_FIRED_BY_UPDATE
(
CurrentTriggerData
->
tg_event
))
newtuple
=
CurrentTriggerData
->
tg_newtuple
;
trigtuple
=
CurrentTriggerData
->
tg_trigtuple
;
rel
=
CurrentTriggerData
->
tg_relation
;
relname
=
SPI_getrelname
(
rel
);
/* check if TT is OFF for this relation */
for
(
i
=
0
;
i
<
nTTOff
;
i
++
)
if
(
strcasecmp
(
TTOff
[
i
],
relname
)
==
0
)
break
;
if
(
i
<
nTTOff
)
/* OFF - nothing to do */
{
pfree
(
relname
);
return
((
newtuple
!=
NULL
)
?
newtuple
:
trigtuple
);
}
trigger
=
CurrentTriggerData
->
tg_trigger
;
if
(
trigger
->
tgnargs
!=
2
)
elog
(
WARN
,
"timetravel (%s): invalid (!= 2) number of arguments %d"
,
relname
,
trigger
->
tgnargs
);
args
=
trigger
->
tgargs
;
tupdesc
=
rel
->
rd_att
;
natts
=
tupdesc
->
natts
;
/*
* Setting CurrentTriggerData to NULL prevents direct calls to trigger
* functions in queries. Normally, trigger functions have to be called
* by trigger manager code only.
*/
CurrentTriggerData
=
NULL
;
for
(
i
=
0
;
i
<
2
;
i
++
)
{
attnum
[
i
]
=
SPI_fnumber
(
tupdesc
,
args
[
i
]);
if
(
attnum
[
i
]
<
0
)
elog
(
WARN
,
"timetravel (%s): there is no attribute %s"
,
relname
,
args
[
i
]);
if
(
SPI_gettypeid
(
tupdesc
,
attnum
[
i
])
!=
ABSTIMEOID
)
elog
(
WARN
,
"timetravel (%s): attributes %s and %s must be of abstime type"
,
relname
,
args
[
0
],
args
[
1
]);
}
oldon
=
SPI_getbinval
(
trigtuple
,
tupdesc
,
attnum
[
0
],
&
isnull
);
if
(
isnull
)
elog
(
WARN
,
"timetravel (%s): %s must be NOT NULL"
,
relname
,
args
[
0
]);
oldoff
=
SPI_getbinval
(
trigtuple
,
tupdesc
,
attnum
[
1
],
&
isnull
);
if
(
isnull
)
elog
(
WARN
,
"timetravel (%s): %s must be NOT NULL"
,
relname
,
args
[
1
]);
/*
* If DELETE/UPDATE of tuple with stop_date neq INFINITY
* then say upper Executor to skip operation for this tuple
*/
if
(
newtuple
!=
NULL
)
/* UPDATE */
{
newon
=
SPI_getbinval
(
newtuple
,
tupdesc
,
attnum
[
0
],
&
isnull
);
if
(
isnull
)
elog
(
WARN
,
"timetravel (%s): %s must be NOT NULL"
,
relname
,
args
[
0
]);
newoff
=
SPI_getbinval
(
newtuple
,
tupdesc
,
attnum
[
1
],
&
isnull
);
if
(
isnull
)
elog
(
WARN
,
"timetravel (%s): %s must be NOT NULL"
,
relname
,
args
[
1
]);
if
(
oldon
!=
newon
||
oldoff
!=
newoff
)
elog
(
WARN
,
"timetravel (%s): you can't change %s and/or %s columns (use set_timetravel)"
,
relname
,
args
[
0
],
args
[
1
]);
if
(
newoff
!=
NOEND_ABSTIME
)
{
pfree
(
relname
);
/* allocated in upper executor context */
return
(
NULL
);
}
}
else
if
(
oldoff
!=
NOEND_ABSTIME
)
/* DELETE */
{
pfree
(
relname
);
return
(
NULL
);
}
newoff
=
GetCurrentAbsoluteTime
();
/* Connect to SPI manager */
if
((
ret
=
SPI_connect
())
<
0
)
elog
(
WARN
,
"timetravel (%s): SPI_connect returned %d"
,
relname
,
ret
);
/* Fetch tuple values and nulls */
cvals
=
(
Datum
*
)
palloc
(
natts
*
sizeof
(
Datum
));
cnulls
=
(
char
*
)
palloc
(
natts
*
sizeof
(
char
));
for
(
i
=
0
;
i
<
natts
;
i
++
)
{
cvals
[
i
]
=
SPI_getbinval
((
newtuple
!=
NULL
)
?
newtuple
:
trigtuple
,
tupdesc
,
i
+
1
,
&
isnull
);
cnulls
[
i
]
=
(
isnull
)
?
'n'
:
' '
;
}
/* change date column(s) */
if
(
newtuple
)
/* UPDATE */
{
cvals
[
attnum
[
0
]
-
1
]
=
newoff
;
/* start_date eq current date */
cnulls
[
attnum
[
0
]
-
1
]
=
' '
;
cvals
[
attnum
[
1
]
-
1
]
=
NOEND_ABSTIME
;
/* stop_date eq INFINITY */
cnulls
[
attnum
[
1
]
-
1
]
=
' '
;
}
else
/* DELETE */
{
cvals
[
attnum
[
1
]
-
1
]
=
newoff
;
/* stop_date eq current date */
cnulls
[
attnum
[
1
]
-
1
]
=
' '
;
}
/*
* Construct ident string as TriggerName $ TriggeredRelationId
* and try to find prepared execution plan.
*/
sprintf
(
ident
,
"%s$%u"
,
trigger
->
tgname
,
rel
->
rd_id
);
plan
=
find_plan
(
ident
,
&
Plans
,
&
nPlans
);
/* if there is no plan ... */
if
(
plan
->
splan
==
NULL
)
{
void
*
pplan
;
Oid
*
ctypes
;
char
sql
[
8192
];
/* allocate ctypes for preparation */
ctypes
=
(
Oid
*
)
palloc
(
natts
*
sizeof
(
Oid
));
/*
* Construct query:
* INSERT INTO _relation_ VALUES ($1, ...)
*/
sprintf
(
sql
,
"INSERT INTO %s VALUES ("
,
relname
);
for
(
i
=
1
;
i
<=
natts
;
i
++
)
{
sprintf
(
sql
+
strlen
(
sql
),
"$%d%s"
,
i
,
(
i
<
natts
)
?
", "
:
")"
);
ctypes
[
i
-
1
]
=
SPI_gettypeid
(
tupdesc
,
i
);
}
/* Prepare plan for query */
pplan
=
SPI_prepare
(
sql
,
natts
,
ctypes
);
if
(
pplan
==
NULL
)
elog
(
WARN
,
"timetravel (%s): SPI_prepare returned %d"
,
relname
,
SPI_result
);
/*
* Remember that SPI_prepare places plan in current memory context
* - so, we have to save plan in Top memory context for latter
* use.
*/
pplan
=
SPI_saveplan
(
pplan
);
if
(
pplan
==
NULL
)
elog
(
WARN
,
"timetravel (%s): SPI_saveplan returned %d"
,
relname
,
SPI_result
);
plan
->
splan
=
pplan
;
}
/*
* Ok, execute prepared plan.
*/
ret
=
SPI_execp
(
plan
->
splan
,
cvals
,
cnulls
,
0
);
if
(
ret
<
0
)
elog
(
WARN
,
"timetravel (%s): SPI_execp returned %d"
,
relname
,
ret
);
/* Tuple to return to upper Executor ... */
if
(
newtuple
)
/* UPDATE */
{
HeapTuple
tmptuple
;
tmptuple
=
SPI_copytuple
(
trigtuple
);
rettuple
=
SPI_modifytuple
(
rel
,
tmptuple
,
1
,
&
(
attnum
[
1
]),
&
newoff
,
NULL
);
/*
* SPI_copytuple allocates tmptuple in upper executor context -
* have to free allocation using SPI_pfree
*/
SPI_pfree
(
tmptuple
);
}
else
/* DELETE */
rettuple
=
trigtuple
;
SPI_finish
();
/* don't forget say Bye to SPI mgr */
pfree
(
relname
);
return
(
rettuple
);
}
/*
* set_timetravel () --
* turn timetravel for specified relation ON/OFF
*/
int32
set_timetravel
(
Name
relname
,
int32
on
)
{
char
*
rname
;
char
*
d
;
char
*
s
;
int
i
;
for
(
i
=
0
;
i
<
nTTOff
;
i
++
)
if
(
namestrcmp
(
relname
,
TTOff
[
i
])
==
0
)
break
;
if
(
i
<
nTTOff
)
/* OFF currently */
{
if
(
on
==
0
)
return
(
0
);
/* turn ON */
free
(
TTOff
[
i
]);
if
(
nTTOff
==
1
)
free
(
TTOff
);
else
{
if
(
i
<
nTTOff
-
1
)
memcpy
(
&
(
TTOff
[
i
]),
&
(
TTOff
[
i
+
1
]),
(
nTTOff
-
i
)
*
sizeof
(
char
*
));
TTOff
=
realloc
(
TTOff
,
(
nTTOff
-
1
)
*
sizeof
(
char
*
));
}
nTTOff
--
;
return
(
0
);
}
/* ON currently */
if
(
on
!=
0
)
return
(
1
);
/* turn OFF */
if
(
nTTOff
==
0
)
TTOff
=
malloc
(
sizeof
(
char
*
));
else
TTOff
=
realloc
(
TTOff
,
(
nTTOff
+
1
)
*
sizeof
(
char
*
));
s
=
rname
=
nameout
(
relname
);
d
=
TTOff
[
nTTOff
]
=
malloc
(
strlen
(
rname
)
+
1
);
while
(
*
s
)
*
d
++
=
tolower
(
*
s
++
);
*
d
=
0
;
pfree
(
rname
);
nTTOff
++
;
return
(
1
);
}
AbsoluteTime
currabstime
()
{
return
(
GetCurrentAbsoluteTime
());
}
static
EPlan
*
find_plan
(
char
*
ident
,
EPlan
**
eplan
,
int
*
nplans
)
{
EPlan
*
newp
;
int
i
;
if
(
*
nplans
>
0
)
{
for
(
i
=
0
;
i
<
*
nplans
;
i
++
)
{
if
(
strcmp
((
*
eplan
)[
i
].
ident
,
ident
)
==
0
)
break
;
}
if
(
i
!=
*
nplans
)
return
(
*
eplan
+
i
);
*
eplan
=
(
EPlan
*
)
realloc
(
*
eplan
,
(
i
+
1
)
*
sizeof
(
EPlan
));
newp
=
*
eplan
+
i
;
}
else
{
newp
=
*
eplan
=
(
EPlan
*
)
malloc
(
sizeof
(
EPlan
));
(
*
nplans
)
=
i
=
0
;
}
newp
->
ident
=
(
char
*
)
malloc
(
strlen
(
ident
)
+
1
);
strcpy
(
newp
->
ident
,
ident
);
newp
->
splan
=
NULL
;
(
*
nplans
)
++
;
return
(
newp
);
}
contrib/spi/timetravel.example
0 → 100644
浏览文件 @
af5c86e2
drop table tttest;
create table tttest (
price_id int4,
price_val int4,
price_on abstime default currabstime(),
price_off abstime default 'infinity'
);
insert into tttest values (1, 1, null, null);
insert into tttest values (2, 2, null, null);
insert into tttest values (3, 3, null, null);
create trigger timetravel
before delete or update on tttest
for each row
execute procedure
timetravel (price_on, price_off);
select * from tttest;
delete from tttest where price_id = 2;
select * from tttest;
-- what do we see ?
-- get current prices
select * from tttest where price_off = 'infinity';
-- change price for price_id == 3
update tttest set price_val = 30 where price_id = 3;
select * from tttest;
-- now we want to change price_id from 3 to 5 in ALL tuples
-- but this gets us not what we need
update tttest set price_id = 5 where price_id = 3;
select * from tttest;
-- restore data as before last update:
select set_timetravel('tttest', 0); -- turn TT OFF!
delete from tttest where price_id = 5;
update tttest set price_off = 'infinity' where price_val = 30;
select * from tttest;
-- and try change price_id now!
update tttest set price_id = 5 where price_id = 3;
select * from tttest;
-- isn't it what we need ?
select set_timetravel('tttest', 1); -- turn TT ON!
-- we want to correct some date
update tttest set price_on = 'Jan-01-1990 00:00:01' where price_id = 5 and
price_off <> 'infinity';
-- but this doesn't work
-- try in this way
select set_timetravel('tttest', 0); -- turn TT OFF!
update tttest set price_on = '01-Jan-1990 00:00:01' where price_id = 5 and
price_off <> 'infinity';
select * from tttest;
-- isn't it what we need ?
-- get price for price_id == 5 as it was '10-Jan-1990'
select * from tttest where price_id = 5 and
price_on <= '10-Jan-1990' and price_off > '10-Jan-1990';
contrib/spi/timetravel.source
0 → 100644
浏览文件 @
af5c86e2
DROP FUNCTION currabstime();
DROP FUNCTION timetravel();
DROP FUNCTION set_timetravel(name, int4);
CREATE FUNCTION currabstime()
RETURNS abstime
AS '_OBJWD_/timetravel_DLSUFFIX_'
LANGUAGE 'c';
CREATE FUNCTION timetravel()
RETURNS opaque
AS '_OBJWD_/timetravel_DLSUFFIX_'
LANGUAGE 'c';
CREATE FUNCTION set_timetravel(name, int4)
RETURNS int4
AS '_OBJWD_/timetravel_DLSUFFIX_'
LANGUAGE 'c';
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录