Skip to content

Commit

Permalink
feat: tags from config, and in log output (#100)
Browse files Browse the repository at this point in the history
* tags config

* tags from config?

* filter out empties

* remove duplicate prop

* fix: only show tags for given oid

Co-authored-by: Misha Kaletsky <[email protected]>
  • Loading branch information
mmkal and mmkal authored Aug 19, 2021
1 parent 4977042 commit d05b6db
Show file tree
Hide file tree
Showing 6 changed files with 311 additions and 9 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"predocker-apply": "yarn docker-copy-query",
"docker-apply": "yarn psql -f /queries/create-git-functions.sql",
"docker-bash": "docker-compose exec postgres bash",
"docker-logs": "docker-compose logs --follow --tail 100",
"docker-psql": "docker-compose exec postgres psql -h localhost -U postgres postgres",
"predocker-copy-query": "yarn docker-exec mkdir -p /queries",
"docker-copy-query": "docker cp queries/create-git-functions.sql plv8-git_postgres_1:/queries",
Expand Down
123 changes: 123 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ This query will return:
"author": "pguser ([email protected])",
"timestamp": "2000-12-25T12:00:00.000Z",
"oid": "[oid]",
"tags": [],
"changes": [
{
"field": "text",
Expand All @@ -125,6 +126,7 @@ This query will return:
"author": "pguser ([email protected])",
"timestamp": "2000-12-25T12:00:00.000Z",
"oid": "[oid]",
"tags": [],
"changes": [
{
"field": "id",
Expand Down Expand Up @@ -262,6 +264,7 @@ where identifier->>'id' = '1'
"author": "pguser ([email protected])",
"timestamp": "2000-12-25T12:00:00.000Z",
"oid": "[oid]",
"tags": [],
"changes": [
{
"field": "text",
Expand All @@ -275,6 +278,7 @@ where identifier->>'id' = '1'
"author": "pguser ([email protected])",
"timestamp": "2000-12-25T12:00:00.000Z",
"oid": "[oid]",
"tags": [],
"changes": [
{
"field": "id",
Expand Down Expand Up @@ -325,6 +329,7 @@ where id = 2
"author": "Alice ([email protected])",
"timestamp": "2000-12-25T12:00:00.000Z",
"oid": "[oid]",
"tags": [],
"changes": [
{
"field": "id",
Expand Down Expand Up @@ -366,6 +371,7 @@ where id = 201
"author": "Bob ([email protected])",
"timestamp": "2000-12-25T12:00:00.000Z",
"oid": "[oid]",
"tags": [],
"changes": [
{
"field": "id",
Expand Down Expand Up @@ -408,6 +414,7 @@ where id = 2
"author": "pguser ([email protected])",
"timestamp": "2000-12-25T12:00:00.000Z",
"oid": "[oid]",
"tags": [],
"changes": [
{
"field": "text",
Expand Down Expand Up @@ -443,6 +450,16 @@ set
where id = 3;
```

Or, set them in git config as a colon-separated list:

```sql
select git_set_local_config('tags', 'your_app_request_id=1234:your_app_trace_id=5678');

update test_table
set text = 'item 3 yet another value'
where id = 3;
```

### Restoring previous versions

`git_resolve` gives you a json representation of a prior version of a row, which can be used for backup and restore. The first argument is a `git` json value, the second value is a valid git ref string (e.g. a git oid returned by `git_log`, or `HEAD`, or `main`. Note that an issue with [isomorphic-git](https://github.com/isomorphic-git/isomorphic-git/issues/1238) means that you can't currently pass values like `HEAD~1` here).
Expand Down Expand Up @@ -488,6 +505,91 @@ returning id, text

If you used `tags` as described above, you can take advantage of them to restore to a known-good state easily:

```sql
select git_log(git)
from test_table
where id = 3
```

```json
[
{
"git_log": [
{
"message": "test_table_git_track_trigger: BEFORE UPDATE ROW on public.test_table",
"author": "pguser ([email protected])",
"timestamp": "2000-12-25T12:00:00.000Z",
"oid": "[oid]",
"tags": [
"your_app_request_id=1234",
"your_app_trace_id=5678"
],
"changes": [
{
"field": "text",
"new": "item 3 yet another value",
"old": "item 3 new year value"
}
]
},
{
"message": "test_table_git_track_trigger: BEFORE UPDATE ROW on public.test_table",
"author": "pguser ([email protected])",
"timestamp": "2000-12-25T12:00:00.000Z",
"oid": "[oid]",
"tags": [
"2001",
"2001-01",
"2001-01-01"
],
"changes": [
{
"field": "text",
"new": "item 3 new year value",
"old": "item 3 boxing day value"
}
]
},
{
"message": "test_table_git_track_trigger: BEFORE UPDATE ROW on public.test_table",
"author": "pguser ([email protected])",
"timestamp": "2000-12-25T12:00:00.000Z",
"oid": "[oid]",
"tags": [
"2000",
"2000-12",
"2000-12-26"
],
"changes": [
{
"field": "text",
"new": "item 3 boxing day value",
"old": "item 3 xmas day value"
}
]
},
{
"message": "test_table_git_track_trigger: BEFORE INSERT ROW on public.test_table",
"author": "pguser ([email protected])",
"timestamp": "2000-12-25T12:00:00.000Z",
"oid": "[oid]",
"tags": [],
"changes": [
{
"field": "id",
"new": 3
},
{
"field": "text",
"new": "item 3 xmas day value"
}
]
}
]
}
]
```

```sql
update test_table set (id, text) =
(
Expand All @@ -508,6 +610,26 @@ returning id, text
}
```

```sql
update test_table set (id, text) =
(
select id, text
from json_populate_record(
null::test_table,
git_resolve(git, ref := 'your_app_request_id=1234')
)
)
where id = 3
returning id, text
```

```json
{
"id": 3,
"text": "item 3 yet another value"
}
```

A similar technique can restore a deleted item:

```sql
Expand Down Expand Up @@ -571,6 +693,7 @@ where git = 'https://github.com/mmkal/plv8-git.git'
"author": "pguser ([email protected])",
"timestamp": "2000-12-25T12:00:00.000Z",
"oid": "[oid]",
"tags": [],
"changes": [
{
"field": "git",
Expand Down
43 changes: 34 additions & 9 deletions src/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as git from 'isomorphic-git'
import * as serializer from './serializer'
import {PG_Vars} from './pg-types'
import {setupMemfs} from './fs'
import {memoizeAsync} from './memoize'

function writeGitFiles(gitFiles: any, fs: memfs.IFs) {
if (!gitFiles) {
Expand Down Expand Up @@ -42,7 +43,7 @@ export const rowToRepo = ({OLD, NEW, ...pg}: PG_Vars) => {

const gitParams = NEW?.[repoColumn] || {}

const commitMessage = `${pg.TG_NAME}: ${pg.TG_WHEN} ${pg.TG_OP} ${pg.TG_LEVEL} on ${pg.TG_TABLE_SCHEMA}.${pg.TG_TABLE_NAME}`.trim()
const defaultCommitMessage = `${pg.TG_NAME}: ${pg.TG_WHEN} ${pg.TG_OP} ${pg.TG_LEVEL} on ${pg.TG_TABLE_SCHEMA}.${pg.TG_TABLE_NAME}`.trim()

return Promise.resolve()
.then(setupGitFolder)
Expand All @@ -60,20 +61,31 @@ export const rowToRepo = ({OLD, NEW, ...pg}: PG_Vars) => {
.then(() =>
git.commit({
...repo,
message: [gitParams.commit?.message, commitMessage].filter(Boolean).join('\n\n'),
message: [
gitParams.commit?.message,
getSetting('commit.message'),
defaultCommitMessage,
getSetting('commit.message.signature'),
]
.filter(Boolean)
.join('\n\n'),
author: {
name: gitParams.commit?.author?.name || getSetting('user.name') || 'pguser',
email: gitParams.commit?.author?.email || getSetting('user.email') || '[email protected]',
},
}),
)
.then(commit =>
Promise.all(
(gitParams.tags || []).map((tag: string) => {
.then(commit => {
const allTags: string[] = [
...(getSetting('tags')?.split(':') || []), // colon separated tags from config
...(gitParams.tags || []),
].filter(Boolean)
return Promise.all(
allTags.map((tag: string) => {
return git.tag({...repo, ref: tag, object: commit})
}),
),
)
)
})
})
.then(() => {
const files: Record<string, number[]> = {}
Expand All @@ -98,7 +110,7 @@ declare const plv8: {
const getSetting = (name: string) => {
// https://www.postgresql.org/docs/9.4/functions-admin.html
const [{git_get_config}] = plv8.execute('select git_get_config($1)', [name])
return git_get_config
return git_get_config as string | null
}

type TreeInfo = {type: string; content: string; oid: string}
Expand All @@ -113,6 +125,10 @@ export const gitLog = (gitRepoJson: object, depth?: number) => {
const {fs} = setupMemfs()
const repo = {fs, dir: '/repo'}

// `listTags` lists all tags for the repo. so we need to use resolveRef to check that each tags is pointing at a given id
// this can mean a lot of repeated calls.
const resolveTagRef = memoizeAsync(git.resolveRef)

return Promise.resolve()
.then(() => writeGitFiles(gitRepoJson, fs))
.then(() => git.log({...repo, depth}))
Expand All @@ -130,11 +146,20 @@ export const gitLog = (gitRepoJson: object, depth?: number) => {
)
},
})
.then((results: WalkResult[]) => ({
.then((results: WalkResult[]) => {
return git.listTags({...repo}).then(tags => {
return Promise.all(tags.map(t => resolveTagRef({...repo, ref: t}))).then(resolvedTags => {
const filteredTags = tags.filter((t, i) => resolvedTags[i] === e.oid)
return {results, tags: filteredTags}
})
})
})
.then(({results, tags}) => ({
message: e.commit.message.trim(),
author: `${e.commit.author.name} (${e.commit.author.email})`,
timestamp: new Date(e.commit.author.timestamp * 1000).toISOString(),
oid: e.oid,
tags,
changes: results
.filter(
r => r.ChildInfo?.type === 'blob' && r.filepath !== '.' && r.ChildInfo.oid !== r.ParentInfo?.oid,
Expand Down
14 changes: 14 additions & 0 deletions src/memoize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const memoizeAsync = <A extends any[], T>(fn: (...args: A) => Promise<T>): ((...args: A) => Promise<T>) => {
const cache = new Map<string, T>()
return (...args: A) => {
const key = JSON.stringify(args)
if (cache.has(key)) {
return Promise.resolve(cache.get(key)!)
}

return fn(...args).then(result => {
cache.set(key, result)
return result
})
}
}
14 changes: 14 additions & 0 deletions test/memoize.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {memoizeAsync} from '../src/memoize'

test('memoize', async () => {
const mock = jest.fn(async () => Math.random())

const memoized = memoizeAsync(mock)

const first = await memoized()
const second = await memoized()

expect([first, second]).toEqual([expect.any(Number), expect.any(Number)])
expect(mock).toHaveBeenCalledTimes(1)
expect(second).toEqual(first)
})
Loading

0 comments on commit d05b6db

Please sign in to comment.