Skip to content

Commit

Permalink
fix: updatedat field not updating on mutations (#1271)
Browse files Browse the repository at this point in the history
  • Loading branch information
davenewza authored Oct 30, 2023
1 parent ab4b5a8 commit 9afd01f
Show file tree
Hide file tree
Showing 13 changed files with 125 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"trailingComma": "es5"
}
28 changes: 28 additions & 0 deletions integration/testdata/built_in_actions/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,30 @@ test("update action", async () => {
expect(updatedPost.subTitle).toEqual("opm");
});

test("update action - updatedAt set", async () => {
const post = await models.post.create({
title: "watermelon",
subTitle: "opm",
});

expect(post.updatedAt).not.toBeNull();
expect(post.updatedAt).toEqual(post.createdAt);

await delay(100);

const updatedPost = await actions.updatePost({
where: { id: post.id },
values: { title: "big watermelon" },
});

expect(updatedPost.updatedAt.valueOf()).toBeGreaterThanOrEqual(
post.createdAt.valueOf() + 100
);
expect(updatedPost.updatedAt.valueOf()).toBeLessThan(
post.createdAt.valueOf() + 1000
);
});

test("update action - explicit set / args", async () => {
const post = await models.post.create({
title: "watermelon",
Expand All @@ -260,3 +284,7 @@ test("update action - explicit set / args", async () => {

expect(updatedPost.title).toEqual("a really cool title");
});

function delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
38 changes: 38 additions & 0 deletions integration/testdata/with_custom_function_hooks/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,44 @@ describe("write hooks", () => {
})
).toHaveAuthorizationError();
});

test("update.beforeWrite hook - updatedAt set", async () => {
const identity = await models.identity.create({
email: "[email protected]",
});
const name = "Alice";

const person = await models.person.create({
sex: Sex.Female,
title: name,
});

expect(person.updatedAt).not.toBeNull();
expect(person.updatedAt).toEqual(person.createdAt);

await delay(100);

const record = await actions
.withIdentity(identity)
.updatePersonWithBeforeWrite({
where: { id: person.id },
values: {
title: "Alice",
sex: Sex.Female,
},
});

expect(record.updatedAt.valueOf()).toBeGreaterThanOrEqual(
person.createdAt.valueOf() + 100
);
expect(record.updatedAt.valueOf()).toBeLessThan(
person.createdAt.valueOf() + 1000
);
});

function delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
});

describe("query hooks", () => {
Expand Down
11 changes: 11 additions & 0 deletions migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ var (

//go:embed set_trace_id.sql
setTraceId string

//go:embed set_updated_at.sql
setUpdatedAt string
)

type DatabaseChange struct {
Expand Down Expand Up @@ -95,6 +98,8 @@ func (m *Migrations) Apply(ctx context.Context) error {
sql.WriteString("\n")
sql.WriteString(setTraceId)
sql.WriteString("\n")
sql.WriteString(setUpdatedAt)
sql.WriteString("\n")

sql.WriteString("CREATE TABLE IF NOT EXISTS keel_schema ( schema TEXT NOT NULL );\n")
sql.WriteString("DELETE FROM keel_schema;\n")
Expand Down Expand Up @@ -223,6 +228,12 @@ func New(ctx context.Context, schema *proto.Schema, database db.Database) (*Migr
return nil, err
}
statements = append(statements, stmt)

stmt, err = createUpdatedAtTriggerStmts(triggers, schema, model)
if err != nil {
return nil, err
}
statements = append(statements, stmt)
}
}

Expand Down
6 changes: 6 additions & 0 deletions migrations/set_updated_at.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CREATE OR REPLACE FUNCTION set_updated_at() RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END
$$ LANGUAGE plpgsql;
15 changes: 15 additions & 0 deletions migrations/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,3 +338,18 @@ func createAuditTriggerStmts(triggers []*TriggerRow, schema *proto.Schema, model

return strings.Join(statements, "\n"), nil
}

// createUpdatedAtTriggerStmts generates the CREATE TRIGGER statements for automatically updating each model's updatedAt column.
// Only creates a trigger if the trigger does not already exist in the database.
func createUpdatedAtTriggerStmts(triggers []*TriggerRow, schema *proto.Schema, model *proto.Model) (string, error) {
modelLower := casing.ToSnake(model.Name)
statements := []string{}

updatedAt := fmt.Sprintf("%s_updated_at", modelLower)
if _, found := lo.Find(triggers, func(t *TriggerRow) bool { return t.TriggerName == updatedAt && t.TableName == modelLower }); !found {
statements = append(statements, fmt.Sprintf(
`CREATE TRIGGER %s BEFORE UPDATE ON %s FOR EACH ROW EXECUTE PROCEDURE set_updated_at();`, updatedAt, Identifier(model.Name)))
}

return strings.Join(statements, "\n"), nil
}
2 changes: 2 additions & 0 deletions migrations/testdata/composite_unique_initial.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ ALTER TABLE "person" ADD CONSTRAINT person_thing_a_thing_b_thing_c_udx UNIQUE ("
CREATE TRIGGER person_create AFTER INSERT ON "person" REFERENCING NEW TABLE AS new_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER person_update AFTER UPDATE ON "person" REFERENCING NEW TABLE AS new_table OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER person_delete AFTER DELETE ON "person" REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER person_updated_at BEFORE UPDATE ON "person" FOR EACH ROW EXECUTE PROCEDURE set_updated_at();
CREATE TRIGGER identity_create AFTER INSERT ON "identity" REFERENCING NEW TABLE AS new_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER identity_update AFTER UPDATE ON "identity" REFERENCING NEW TABLE AS new_table OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER identity_delete AFTER DELETE ON "identity" REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER identity_updated_at BEFORE UPDATE ON "identity" FOR EACH ROW EXECUTE PROCEDURE set_updated_at();

===

Expand Down
2 changes: 2 additions & 0 deletions migrations/testdata/default_value_initial.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,11 @@ ALTER TABLE "person" ADD CONSTRAINT person_id_pkey PRIMARY KEY ("id");
CREATE TRIGGER person_create AFTER INSERT ON "person" REFERENCING NEW TABLE AS new_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER person_update AFTER UPDATE ON "person" REFERENCING NEW TABLE AS new_table OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER person_delete AFTER DELETE ON "person" REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER person_updated_at BEFORE UPDATE ON "person" FOR EACH ROW EXECUTE PROCEDURE set_updated_at();
CREATE TRIGGER identity_create AFTER INSERT ON "identity" REFERENCING NEW TABLE AS new_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER identity_update AFTER UPDATE ON "identity" REFERENCING NEW TABLE AS new_table OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER identity_delete AFTER DELETE ON "identity" REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER identity_updated_at BEFORE UPDATE ON "identity" FOR EACH ROW EXECUTE PROCEDURE set_updated_at();

===

Expand Down
2 changes: 2 additions & 0 deletions migrations/testdata/initial.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@ ALTER TABLE "person" ADD CONSTRAINT person_id_pkey PRIMARY KEY ("id");
CREATE TRIGGER person_create AFTER INSERT ON "person" REFERENCING NEW TABLE AS new_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER person_update AFTER UPDATE ON "person" REFERENCING NEW TABLE AS new_table OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER person_delete AFTER DELETE ON "person" REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER person_updated_at BEFORE UPDATE ON "person" FOR EACH ROW EXECUTE PROCEDURE set_updated_at();
CREATE TRIGGER identity_create AFTER INSERT ON "identity" REFERENCING NEW TABLE AS new_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER identity_update AFTER UPDATE ON "identity" REFERENCING NEW TABLE AS new_table OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER identity_delete AFTER DELETE ON "identity" REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER identity_updated_at BEFORE UPDATE ON "identity" FOR EACH ROW EXECUTE PROCEDURE set_updated_at();

===

Expand Down
2 changes: 2 additions & 0 deletions migrations/testdata/model_added.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ CREATE TABLE "animal" (
ALTER TABLE "animal" ADD CONSTRAINT animal_id_pkey PRIMARY KEY ("id");
ALTER TABLE "animal" ADD FOREIGN KEY ("human_friend_id") REFERENCES "person"("id") ON DELETE CASCADE;


CREATE TRIGGER animal_create AFTER INSERT ON "animal" REFERENCING NEW TABLE AS new_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER animal_update AFTER UPDATE ON "animal" REFERENCING NEW TABLE AS new_table OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER animal_delete AFTER DELETE ON "animal" REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER animal_updated_at BEFORE UPDATE ON "animal" FOR EACH ROW EXECUTE PROCEDURE set_updated_at();

===

Expand Down
2 changes: 2 additions & 0 deletions migrations/testdata/simple_field_types.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,11 @@ ALTER TABLE "person" ADD CONSTRAINT person_id_pkey PRIMARY KEY ("id");
CREATE TRIGGER person_create AFTER INSERT ON "person" REFERENCING NEW TABLE AS new_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER person_update AFTER UPDATE ON "person" REFERENCING NEW TABLE AS new_table OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER person_delete AFTER DELETE ON "person" REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER person_updated_at BEFORE UPDATE ON "person" FOR EACH ROW EXECUTE PROCEDURE set_updated_at();
CREATE TRIGGER identity_create AFTER INSERT ON "identity" REFERENCING NEW TABLE AS new_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER identity_update AFTER UPDATE ON "identity" REFERENCING NEW TABLE AS new_table OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER identity_delete AFTER DELETE ON "identity" REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE PROCEDURE process_audit();
CREATE TRIGGER identity_updated_at BEFORE UPDATE ON "identity" FOR EACH ROW EXECUTE PROCEDURE set_updated_at();

===

Expand Down
13 changes: 12 additions & 1 deletion packages/testing-runtime/src/Executor.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,19 @@ export class Executor {
}

if (this._parseJsonResult) {
return r.json();
return r.text().then((t) => {
return JSON.parse(t, reviver);
});
}
});
}
}

const dateFormat = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:(\d{2}(?:\.\d*))Z$/;

function reviver(key, value) {
if (typeof value === "string" && dateFormat.test(value)) {
return new Date(value);
}
return value;
}
3 changes: 2 additions & 1 deletion runtime/runtime_events_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,8 @@ func TestUpdateEvent(t *testing.T) {

updatedAt, err := time.Parse("2006-01-02T15:04:05.999999999-07:00", data.String("updatedAt"))
require.NoError(t, err)
require.Equal(t, wedding["updatedAt"], updatedAt)
require.NotEqual(t, wedding["updatedAt"], updatedAt)
require.Equal(t, updatedWedding["updatedAt"], updatedAt)
}

func TestDeleteEvent(t *testing.T) {
Expand Down

0 comments on commit 9afd01f

Please sign in to comment.