具有 git 语义的数据库 table 的最佳模式?
Best patterns for database table with git semantics?
我们有一些中型到大型 tables(即数十万行)的数据,我们希望允许用户有效地 "fork" 类似于 Git,并可能随着时间的推移进行合作。我们希望他们能够多次 "fork" 它们,并对不同的分叉进行编辑并比较两个分叉之间的各种聚合数据。
基本上,这些数据开始时是一堆只读 table,我们希望让用户能够查看包含他们编辑的 table。挑战在于我们还想定期查询此 table 中的行(即仅显示 column3 = 'left' 的行),甚至可能根据特定列加入另一个 table . (即 user_table.column3 = other_table.column10 的 INNER JOIN)——即我们希望能够像对待完全物化的 table 一样对待这些 table关系操作。
最愚蠢的解决方案(我们今天所做的)就是简单地制作 table 的完整副本,但挑战在于至少在我们当前的化身中这是昂贵的:我们正在使用 PostgreSQL,这些复制操作可能需要 2-20 分钟。我们希望这是一个实时操作,就像具有写时复制行为的东西。
我们确实记录了用户所做的更改(即更改日志),因此我们最终可以将它们应用到 "original" table 但最好有一个模式,或者在一个理想的世界,一个库或存储层,只是为我们做这件事。
我们今天碰巧使用 PostgreSQL 和 Python,但我对这里的 NoSQL 系统持开放态度,因为我可以想象这会导致一些非常讨厌的事情 SQL 如果这足够概括的话。另外,我们愿意牺牲一些关系能力来实现上述目标。在这个 space 中是否有已知的模式 and/or 实现?是在 PostgreSQL 中,还是在其他存储系统中?事实证明,这对 google 来说是一件非常困难的事情。
当然可以用它做一些事情,尽管结果可能(或可能不会)不稳定,具体取决于您将在数据库中使用多少其他 hack。
如果我们将列 owner
和 deleted
添加到源 table,创建一个视图,将 INSTEAD OF
触发器添加到视图并仅授予用户权限到视图而不是源 table 我们得到这个:
CREATE SEQUENCE source_seq;
CREATE TABLE source
(
id INT DEFAULT nextval('source_seq')
,value VARCHAR
,owner name DEFAULT session_user
,deleted boolean DEFAULT FALSE
);
CREATE VIEW source_emp AS
SELECT id, value
FROM source AS s1
WHERE ((owner IS NULL AND NOT EXISTS (SELECT * FROM source AS s2 WHERE s1.id = s2.id AND s2.owner = session_user )) OR owner = session_user)
AND NOT deleted
CREATE OR REPLACE FUNCTION source_change()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER
AS $function$
BEGIN
IF TG_OP = 'UPDATE' THEN
INSERT INTO source(id,value,owner,deleted)
SELECT NEW.id,NEW.value,session_user, FALSE
WHERE NOT EXISTS(SELECT * FROM source WHERE owner = session_user AND id = OLD.id);
UPDATE source SET id = NEW.id, value = NEW.value, owner = session_user WHERE owner = session_user AND id = OLD.id;
RETURN NEW;
ELSIF TG_OP = 'DELETE' THEN
INSERT INTO source(id,value,owner,deleted)
SELECT OLD.id,NULL,session_user, TRUE
WHERE NOT EXISTS(SELECT * FROM source WHERE owner = session_user AND id = OLD.id);
UPDATE source SET value = NULL, deleted = TRUE WHERE owner = session_user AND id = OLD.id;
RETURN NULL;
END IF;
RETURN NEW;
END;
$function$;
CREATE TRIGGER source_trig
INSTEAD OF UPDATE OR DELETE ON
source_emp FOR EACH ROW EXECUTE PROCEDURE source_change();
现在,如果用户尝试:
- INSERT :他获取仅在视图中对他可见的记录
- 更新:他得到原始记录的副本并编辑这些副本或更新他的副本
- 删除:他将源标记为已删除,仅供他个人使用。
如果您不想触及原来的 table,您可以创建一个包含额外两列的新列并更改视图,使其与原来的 table 合并。
我们有一些中型到大型 tables(即数十万行)的数据,我们希望允许用户有效地 "fork" 类似于 Git,并可能随着时间的推移进行合作。我们希望他们能够多次 "fork" 它们,并对不同的分叉进行编辑并比较两个分叉之间的各种聚合数据。
基本上,这些数据开始时是一堆只读 table,我们希望让用户能够查看包含他们编辑的 table。挑战在于我们还想定期查询此 table 中的行(即仅显示 column3 = 'left' 的行),甚至可能根据特定列加入另一个 table . (即 user_table.column3 = other_table.column10 的 INNER JOIN)——即我们希望能够像对待完全物化的 table 一样对待这些 table关系操作。
最愚蠢的解决方案(我们今天所做的)就是简单地制作 table 的完整副本,但挑战在于至少在我们当前的化身中这是昂贵的:我们正在使用 PostgreSQL,这些复制操作可能需要 2-20 分钟。我们希望这是一个实时操作,就像具有写时复制行为的东西。
我们确实记录了用户所做的更改(即更改日志),因此我们最终可以将它们应用到 "original" table 但最好有一个模式,或者在一个理想的世界,一个库或存储层,只是为我们做这件事。
我们今天碰巧使用 PostgreSQL 和 Python,但我对这里的 NoSQL 系统持开放态度,因为我可以想象这会导致一些非常讨厌的事情 SQL 如果这足够概括的话。另外,我们愿意牺牲一些关系能力来实现上述目标。在这个 space 中是否有已知的模式 and/or 实现?是在 PostgreSQL 中,还是在其他存储系统中?事实证明,这对 google 来说是一件非常困难的事情。
当然可以用它做一些事情,尽管结果可能(或可能不会)不稳定,具体取决于您将在数据库中使用多少其他 hack。
如果我们将列 owner
和 deleted
添加到源 table,创建一个视图,将 INSTEAD OF
触发器添加到视图并仅授予用户权限到视图而不是源 table 我们得到这个:
CREATE SEQUENCE source_seq;
CREATE TABLE source
(
id INT DEFAULT nextval('source_seq')
,value VARCHAR
,owner name DEFAULT session_user
,deleted boolean DEFAULT FALSE
);
CREATE VIEW source_emp AS
SELECT id, value
FROM source AS s1
WHERE ((owner IS NULL AND NOT EXISTS (SELECT * FROM source AS s2 WHERE s1.id = s2.id AND s2.owner = session_user )) OR owner = session_user)
AND NOT deleted
CREATE OR REPLACE FUNCTION source_change()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER
AS $function$
BEGIN
IF TG_OP = 'UPDATE' THEN
INSERT INTO source(id,value,owner,deleted)
SELECT NEW.id,NEW.value,session_user, FALSE
WHERE NOT EXISTS(SELECT * FROM source WHERE owner = session_user AND id = OLD.id);
UPDATE source SET id = NEW.id, value = NEW.value, owner = session_user WHERE owner = session_user AND id = OLD.id;
RETURN NEW;
ELSIF TG_OP = 'DELETE' THEN
INSERT INTO source(id,value,owner,deleted)
SELECT OLD.id,NULL,session_user, TRUE
WHERE NOT EXISTS(SELECT * FROM source WHERE owner = session_user AND id = OLD.id);
UPDATE source SET value = NULL, deleted = TRUE WHERE owner = session_user AND id = OLD.id;
RETURN NULL;
END IF;
RETURN NEW;
END;
$function$;
CREATE TRIGGER source_trig
INSTEAD OF UPDATE OR DELETE ON
source_emp FOR EACH ROW EXECUTE PROCEDURE source_change();
现在,如果用户尝试:
- INSERT :他获取仅在视图中对他可见的记录
- 更新:他得到原始记录的副本并编辑这些副本或更新他的副本
- 删除:他将源标记为已删除,仅供他个人使用。
如果您不想触及原来的 table,您可以创建一个包含额外两列的新列并更改视图,使其与原来的 table 合并。