Skip to content

Commit

Permalink
[2] Add eval function for inserting entity history (#319)
Browse files Browse the repository at this point in the history
* Add eval function for inserting entity history

* Fix spelling of variable
  • Loading branch information
JonoPrest authored Nov 18, 2024
1 parent 881a193 commit cc96152
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 44 deletions.
71 changes: 44 additions & 27 deletions codegenerator/cli/templates/dynamic/codegen/src/db/Entities.res.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,20 @@ module EntityHistory = {
type t<'entity> = {
table: table,
createInsertFnQuery: string,
// insertFn: historyRow<'entity> => promise<unit>,
schema: S.t<historyRow<'entity>>,
insertFn: (Postgres.sql, Js.Json.t) => promise<unit>,
}
let insertRow = (self: t<'entity>, ~sql, ~historyRow: historyRow<'entity>) => {
let row = historyRow->S.serializeOrRaiseWith(self.schema)
self.insertFn(sql, row)
}

type entityInternal

external castInternal: t<'entity> => t<entityInternal> = "%identity"

let fromTable = (table: table): t<'entity> => {
let fromTable = (table: table, ~schema: S.t<'entity>): t<'entity> => {
let entity_history_block_timestamp = "entity_history_block_timestamp"
let entity_history_chain_id = "entity_history_chain_id"
let entity_history_block_number = "entity_history_block_number"
Expand Down Expand Up @@ -176,29 +182,30 @@ module EntityHistory = {
~fields=Belt.Array.concatMany([currentHistoryFields, previousHistoryFields, dataFields]),
)

let createInsertFnQuery = {
let insertFnName = `"insert_${table.tableName}"`
let historRowArg = "history_row"
let historyTablePath = `"public"."${historyTableName}"`
let originTablePath = `"public"."${originTableName}"`
let insertFnName = `"insert_${table.tableName}"`
let historyRowArg = "history_row"
let historyTablePath = `"public"."${historyTableName}"`
let originTablePath = `"public"."${originTableName}"`

let previousHistoryFieldsAreNullStr =
previousChangeFieldNames
->Belt.Array.map(fieldName => `${historRowArg}.${fieldName} IS NULL`)
->Js.Array2.joinWith(" OR ")
let previousHistoryFieldsAreNullStr =
previousChangeFieldNames
->Belt.Array.map(fieldName => `${historyRowArg}.${fieldName} IS NULL`)
->Js.Array2.joinWith(" OR ")

let currentChangeFieldNamesCommaSeparated = currentChangeFieldNames->Js.Array2.joinWith(", ")
let currentChangeFieldNamesCommaSeparated = currentChangeFieldNames->Js.Array2.joinWith(", ")

let dataFieldNamesDoubleQuoted = dataFieldNames->Belt.Array.map(fieldName => `"${fieldName}"`)
let dataFieldNamesCommaSeparated = dataFieldNamesDoubleQuoted->Js.Array2.joinWith(", ")
let dataFieldNamesDoubleQuoted = dataFieldNames->Belt.Array.map(fieldName => `"${fieldName}"`)
let dataFieldNamesCommaSeparated = dataFieldNamesDoubleQuoted->Js.Array2.joinWith(", ")

let allFieldNames = Belt.Array.concatMany([
let allFieldNamesDoubleQuoted =
Belt.Array.concatMany([
currentChangeFieldNames,
previousChangeFieldNames,
dataFieldNamesDoubleQuoted,
])
dataFieldNames,
])->Belt.Array.map(fieldName => `"${fieldName}"`)

`CREATE OR REPLACE FUNCTION ${insertFnName}(${historRowArg} ${historyTablePath})
let createInsertFnQuery = {
`CREATE OR REPLACE FUNCTION ${insertFnName}(${historyRowArg} ${historyTablePath})
RETURNS void AS $$
DECLARE
v_previous_record RECORD;
Expand All @@ -209,7 +216,7 @@ module EntityHistory = {
-- Find the most recent record for the same id
SELECT ${currentChangeFieldNamesCommaSeparated} INTO v_previous_record
FROM ${historyTablePath}
WHERE ${id} = ${historRowArg}.${id}
WHERE ${id} = ${historyRowArg}.${id}
ORDER BY ${currentChangeFieldNames
->Belt.Array.map(fieldName => fieldName ++ " DESC")
->Js.Array2.joinWith(", ")}
Expand All @@ -219,12 +226,12 @@ module EntityHistory = {
IF FOUND THEN
${Belt.Array.zip(currentChangeFieldNames, previousChangeFieldNames)
->Belt.Array.map(((currentFieldName, previousFieldName)) => {
`${historRowArg}.${previousFieldName} := v_previous_record.${currentFieldName};`
`${historyRowArg}.${previousFieldName} := v_previous_record.${currentFieldName};`
})
->Js.Array2.joinWith(" ")}
ElSE
-- Check if a value for the id exists in the origin table and if so, insert a history row for it.
SELECT ${dataFieldNamesCommaSeparated} FROM ${originTablePath} WHERE id = ${historRowArg}.${id} INTO v_origin_record;
SELECT ${dataFieldNamesCommaSeparated} FROM ${originTablePath} WHERE id = ${historyRowArg}.${id} INTO v_origin_record;
IF FOUND THEN
INSERT INTO ${historyTablePath} (${currentChangeFieldNamesCommaSeparated}, ${dataFieldNamesCommaSeparated})
-- SET the current change data fields to 0 since we don't know what they were
Expand All @@ -237,23 +244,33 @@ module EntityHistory = {

${previousChangeFieldNames
->Belt.Array.map(previousFieldName => {
`${historRowArg}.${previousFieldName} := 0;`
`${historyRowArg}.${previousFieldName} := 0;`
})
->Js.Array2.joinWith(" ")}
END IF;
END IF;
END IF;

INSERT INTO ${historyTablePath} (${allFieldNames->Js.Array2.joinWith(", ")})
VALUES (${allFieldNames
->Belt.Array.map(fieldName => `${historRowArg}.${fieldName}`)
INSERT INTO ${historyTablePath} (${allFieldNamesDoubleQuoted->Js.Array2.joinWith(", ")})
VALUES (${allFieldNamesDoubleQuoted
->Belt.Array.map(fieldName => `${historyRowArg}.${fieldName}`)
->Js.Array2.joinWith(", ")});
END;
$$ LANGUAGE plpgsql;
`
}

{table, createInsertFnQuery}
let insertFnString = `(sql, rowArgs) =>
sql\`select ${insertFnName}(ROW(${allFieldNamesDoubleQuoted
->Belt.Array.map(fieldNameDoubleQuoted => `\${rowArgs[${fieldNameDoubleQuoted}]\}`)
->Js.Array2.joinWith(", ")}));\``

let insertFn: (Postgres.sql, Js.Json.t) => promise<unit> =
insertFnString->Table.PostgresInterop.eval

let schema = makeHistoryRowSchema(schema)

{table, createInsertFnQuery, schema, insertFn}
}
}

Expand Down Expand Up @@ -321,7 +338,7 @@ module {{entity.name.capitalized}} = {
{{/if}}
)

let entityHistory = table->EntityHistory.fromTable
let entityHistory = table->EntityHistory.fromTable(~schema)
}

{{/each}}
Expand Down
53 changes: 36 additions & 17 deletions scenarios/test_codegen/test/lib_tests/EntityHistory_test.res
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
open RescriptMocha

//unsafe polymorphic toString binding for any type
@send external toStringUnsafe: 'a => string = "toString"

type testEntity = {
id: string,
fieldA: int,
Expand All @@ -25,7 +28,8 @@ let mockEntityTable = Table.mkTable(
Table.mkField("fieldB", Text, ~isNullable=true),
],
)
let mockEntityHistory = Entities.EntityHistory.fromTable(mockEntityTable)

let mockEntityHistory = mockEntityTable->Entities.EntityHistory.fromTable(~schema=testEntitySchema)

let batchSetMockEntity = Table.PostgresInterop.makeBatchSetFn(
~table=mockEntityTable,
Expand Down Expand Up @@ -111,7 +115,7 @@ describe("Entity history serde", () => {
})

describe("Entity History Codegen", () => {
it("Creates an insert function", () => {
it("Creates a postgres insert function", () => {
let expected = `CREATE OR REPLACE FUNCTION "insert_TestEntity_history"(history_row "public"."TestEntity_history")
RETURNS void AS $$
DECLARE
Expand Down Expand Up @@ -144,15 +148,24 @@ describe("Entity History Codegen", () => {
END IF;
END IF;
INSERT INTO "public"."TestEntity_history" (entity_history_block_timestamp, entity_history_chain_id, entity_history_block_number, entity_history_log_index, previous_entity_history_block_timestamp, previous_entity_history_chain_id, previous_entity_history_block_number, previous_entity_history_log_index, "id", "fieldA", "fieldB")
VALUES (history_row.entity_history_block_timestamp, history_row.entity_history_chain_id, history_row.entity_history_block_number, history_row.entity_history_log_index, history_row.previous_entity_history_block_timestamp, history_row.previous_entity_history_chain_id, history_row.previous_entity_history_block_number, history_row.previous_entity_history_log_index, history_row."id", history_row."fieldA", history_row."fieldB");
INSERT INTO "public"."TestEntity_history" ("entity_history_block_timestamp", "entity_history_chain_id", "entity_history_block_number", "entity_history_log_index", "previous_entity_history_block_timestamp", "previous_entity_history_chain_id", "previous_entity_history_block_number", "previous_entity_history_log_index", "id", "fieldA", "fieldB")
VALUES (history_row."entity_history_block_timestamp", history_row."entity_history_chain_id", history_row."entity_history_block_number", history_row."entity_history_log_index", history_row."previous_entity_history_block_timestamp", history_row."previous_entity_history_chain_id", history_row."previous_entity_history_block_number", history_row."previous_entity_history_log_index", history_row."id", history_row."fieldA", history_row."fieldB");
END;
$$ LANGUAGE plpgsql;
`

Assert.equal(expected, mockEntityHistory.createInsertFnQuery)
})

it("Creates a js insert function", () => {
let insertFnString = mockEntityHistory.insertFn->toStringUnsafe

let expected = `(sql, rowArgs) =>
sql\`select "insert_TestEntity_history"(ROW(\${rowArgs["entity_history_block_timestamp"]}, \${rowArgs["entity_history_chain_id"]}, \${rowArgs["entity_history_block_number"]}, \${rowArgs["entity_history_log_index"]}, \${rowArgs["previous_entity_history_block_timestamp"]}, \${rowArgs["previous_entity_history_chain_id"]}, \${rowArgs["previous_entity_history_block_number"]}, \${rowArgs["previous_entity_history_log_index"]}, \${rowArgs["id"]}, \${rowArgs["fieldA"]}, \${rowArgs["fieldB"]}));\``

Assert.equal(expected, insertFnString)
})

Async.it("Creating tables and functions works", async () => {
let _ = await Migrations.runDownMigrations(~shouldExit=false)
let _resA = await Migrations.creatTableIfNotExists(DbFunctions.sql, mockEntityTable)
Expand All @@ -170,22 +183,28 @@ describe("Entity History Codegen", () => {
let blockTimestamp = blockNumber * 15
let logIndex = 1

let entityHistoryItem = {
"entity_id": "1",
"fieldA": 2,
"fieldB": Some("test2"),
"entity_history_chain_id": chainId,
"entity_history_block_number": blockNumber,
"entity_history_block_timestamp": blockTimestamp,
"entity_history_log_index": logIndex,
let entityHistoryItem: Entities.EntityHistory.historyRow<testEntity> = {
current: {
chain_id: chainId,
block_timestamp: blockTimestamp,
block_number: blockNumber,
log_index: logIndex,
},
previous: None,
entityData: {
id: "1",
fieldA: 2,
fieldB: Some("test2"),
},
}

//TODO: this should be created in the entity history module
let query = `(sql, args) => sql\`select "insert_TestEntity_history"(ROW(\${args.entity_history_block_timestamp}, \${args.entity_history_chain_id}, \${args.entity_history_block_number}, \${args.entity_history_log_index}, \${args.previous_entity_history_block_timestamp}, \${args.previous_entity_history_chain_id}, \${args.previous_entity_history_block_number}, \${args.previous_entity_history_log_index}, \${args.entity_id}, \${args.fieldA}, \${args.fieldB}));\``

let call: (Postgres.sql, 'a) => promise<unit> = Table.PostgresInterop.eval(query)
let _callRes =
await mockEntityHistory->Entities.EntityHistory.insertRow(
~sql=DbFunctions.sql,
~historyRow=entityHistoryItem,
)

let _callRes = await DbFunctions.sql->call(entityHistoryItem)
// let _callRes = await DbFunctions.sql->call(entityHistoryItem)

let expectedResult = [
{
Expand Down

0 comments on commit cc96152

Please sign in to comment.