Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf9ea5fbda | ||
|
|
5cdec88299 | ||
|
|
83b215fd25 | ||
|
|
94d776e3de |
@@ -165,3 +165,5 @@ dist
|
|||||||
|
|
||||||
temp/*
|
temp/*
|
||||||
dist/*
|
dist/*
|
||||||
|
# Local build of the CLI binary
|
||||||
|
/pgtidy
|
||||||
|
|||||||
@@ -60,13 +60,28 @@ testdata/corpus/ — real-world .pgsql procedures used as the safety/idempoten
|
|||||||
4. **Graceful degradation**: any span the parser cannot handle is passed through verbatim
|
4. **Graceful degradation**: any span the parser cannot handle is passed through verbatim
|
||||||
rather than corrupted.
|
rather than corrupted.
|
||||||
5. **Runtime safety gate**: every frontend (CLI `fmt`, LSP `textDocument/formatting` and
|
5. **Runtime safety gate**: every frontend (CLI `fmt`, LSP `textDocument/formatting` and
|
||||||
`rangeFormatting`) calls `format.SemanticallyEqual(src, out)` before writing or returning
|
`rangeFormatting`) calls `format.VerifySafe(src, out, style)` before writing or returning
|
||||||
formatted output. It re-lexes both sides and compares non-trivia token streams
|
formatted output. If it returns a non-nil error the formatter has a bug — the caller must
|
||||||
(case-insensitive for identifiers/keywords, exact otherwise, recursing into dollar-quoted
|
refuse to write/emit the result and keep the original source, never guess or best-effort
|
||||||
bodies). If it ever returns false, the formatter has a bug — the caller must refuse to
|
it. This is enforced at runtime, not just in `pkg/format/*_test.go`, so a formatter bug can
|
||||||
write/emit the result and keep the original source, never guess or best-effort it. This is
|
never silently drop or alter code. `VerifySafe` runs four checks:
|
||||||
not just a test assertion (`pkg/format/format_test.go`); it is enforced at runtime so a
|
- **Semantic equivalence** (`SemanticallyEqual`): re-lex both sides, compare the non-trivia
|
||||||
formatter bug can never silently drop or alter code.
|
token streams — case-insensitive for identifiers/keywords, exact otherwise, recursing
|
||||||
|
into dollar-quoted bodies. Comments and whitespace are trivia and are ignored here.
|
||||||
|
- **Comment preservation** (`CommentsPreserved`): every `--` and `/* */` comment in `src`
|
||||||
|
reappears in `out`, in order, with the same content (line endings and indentation are
|
||||||
|
normalised away; dropping, merging, splitting, reordering, or rewording a comment is
|
||||||
|
not). Descends into dollar-quoted bodies.
|
||||||
|
- **Structural balance** (`StructurallyBalanced`): the `( ) [ ]` and BEGIN/CASE/IF/LOOP…END
|
||||||
|
nesting profile of `out` matches `src`, counting only real code tokens (comment and
|
||||||
|
string/dollar-quote contents are skipped).
|
||||||
|
- **Idempotence**: re-formatting `out` yields `out` unchanged.
|
||||||
|
|
||||||
|
**Line endings**: the formatter re-emits all layout with `st.Newline` (default `\n`), so a
|
||||||
|
CRLF input file is normalised to LF on write. This includes `\r\n` that sits *inside* a
|
||||||
|
multi-line string literal — a line-ending change there is layout normalisation, not a change
|
||||||
|
of code content, so `SemanticallyEqual` compares string tokens modulo `\r\n` ↔ `\n`
|
||||||
|
(`normNL` in `pkg/format/safety.go`). Set `newline: "\r\n"` in `.pgtidy.yaml` to keep CRLF.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -63,8 +63,8 @@ func cmdFmt(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
|
|||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
out := format.File(parser.Parse(string(src)), st)
|
out := format.File(parser.Parse(string(src)), st)
|
||||||
if !format.SemanticallyEqual(string(src), out) {
|
if err := format.VerifySafe(string(src), out, st); err != nil {
|
||||||
_, _ = fmt.Fprintln(stderr, "pgtidy: refusing to format stdin: formatter safety check failed (output would change code content)")
|
_, _ = fmt.Fprintf(stderr, "pgtidy: refusing to format stdin: formatter safety check failed: %v\n", err)
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
@@ -90,8 +90,8 @@ func cmdFmt(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
out := format.File(parser.Parse(string(src)), st)
|
out := format.File(parser.Parse(string(src)), st)
|
||||||
if !format.SemanticallyEqual(string(src), out) {
|
if err := format.VerifySafe(string(src), out, st); err != nil {
|
||||||
_, _ = fmt.Fprintf(stderr, "pgtidy: refusing to format %s: formatter safety check failed (output would change code content)\n", path)
|
_, _ = fmt.Fprintf(stderr, "pgtidy: refusing to format %s: formatter safety check failed: %v\n", path, err)
|
||||||
exit = 2
|
exit = 2
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,14 +47,14 @@ insert_collapse_values: true # Fold multiple VALUES rows onto fewer lines
|
|||||||
|
|
||||||
# --- Routines (functions / procedures) ---
|
# --- Routines (functions / procedures) ---
|
||||||
|
|
||||||
align_param_types: true # Pad param names so type column aligns across all params
|
align_param_types: false # Pad param names so type column aligns across all params
|
||||||
routine_as_wrap: true # Newline before AS $$ (false = keep AS on same line)
|
routine_as_wrap: true # Newline before AS $$ (false = keep AS on same line)
|
||||||
|
|
||||||
# --- PL/pgSQL body ---
|
# --- PL/pgSQL body ---
|
||||||
|
|
||||||
plpgsql_max_blank_lines: 1 # Max consecutive blank lines in body
|
plpgsql_max_blank_lines: 1 # Max consecutive blank lines in body
|
||||||
plpgsql_declare_align_type: false # Align type column in DECLARE block
|
plpgsql_declare_align_type: true # Align type column in DECLARE block
|
||||||
plpgsql_declare_align_eq: false # Align := / = in DECLARE block
|
plpgsql_declare_align_eq: true # Align := / = in DECLARE block (padded to the widest assigned declaration)
|
||||||
plpgsql_if_then_newline: true # THEN on its own line (false = same line as condition)
|
plpgsql_if_then_newline: true # THEN on its own line (false = same line as condition)
|
||||||
plpgsql_loop_collapse: true # Collapse empty loop bodies to one line
|
plpgsql_loop_collapse: true # Collapse empty loop bodies to one line
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -128,7 +128,7 @@ DataGrip enum conventions used below:
|
|||||||
| DataGrip key | PgTidy key | Default | Notes |
|
| DataGrip key | PgTidy key | Default | Notes |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `ROUTINE_ARG_COMMA` | uses `commas` | `leading` | same setting as query lists |
|
| `ROUTINE_ARG_COMMA` | uses `commas` | `leading` | same setting as query lists |
|
||||||
| `ROUTINE_ARG_ALIGN_TYPES` | `align_param_types` | `true` | align type column in param list |
|
| `ROUTINE_ARG_ALIGN_TYPES` | `align_param_types` | `false` | align type column in param list |
|
||||||
| `ROUTINE_AS_WRAP` | `routine_as_wrap` | `true` | newline before `AS $$` |
|
| `ROUTINE_AS_WRAP` | `routine_as_wrap` | `true` | newline before `AS $$` |
|
||||||
|
|
||||||
### PL/pgSQL body
|
### PL/pgSQL body
|
||||||
@@ -136,8 +136,8 @@ DataGrip enum conventions used below:
|
|||||||
| DataGrip key | PgTidy key | Default | Notes |
|
| DataGrip key | PgTidy key | Default | Notes |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `IMP_COMMON_KEEP_BLANK_LINES_IN_CODE` | `plpgsql_max_blank_lines` | `1` | max consecutive blank lines in body |
|
| `IMP_COMMON_KEEP_BLANK_LINES_IN_CODE` | `plpgsql_max_blank_lines` | `1` | max consecutive blank lines in body |
|
||||||
| `IMP_DECLARE_ALIGN_TYPE` | `plpgsql_declare_align_type` | `false` | align type column in DECLARE block |
|
| `IMP_DECLARE_ALIGN_TYPE` | `plpgsql_declare_align_type` | `true` | align type column in DECLARE block |
|
||||||
| `IMP_DECLARE_ALIGN_EQ` | `plpgsql_declare_align_eq` | `false` | align `:=` / `=` in DECLARE block |
|
| `IMP_DECLARE_ALIGN_EQ` | `plpgsql_declare_align_eq` | `true` | align `:=` / `=` in DECLARE block |
|
||||||
| `IMP_IF_THEN_WRAP_THEN` | `plpgsql_if_then_newline` | `true` | THEN on its own line |
|
| `IMP_IF_THEN_WRAP_THEN` | `plpgsql_if_then_newline` | `true` | THEN on its own line |
|
||||||
| `IMP_LOOP_COLLAPSE` | `plpgsql_loop_collapse` | `true` | collapse empty loop bodies |
|
| `IMP_LOOP_COLLAPSE` | `plpgsql_loop_collapse` | `true` | collapse empty loop bodies |
|
||||||
|
|
||||||
|
|||||||
+12
-2
@@ -93,6 +93,14 @@ Legend: ✅ done · 🚧 in progress · ⬜ not started
|
|||||||
- `fmt_test.go`: stdin, --check (un/formatted), -w idempotence, unknown-command.
|
- `fmt_test.go`: stdin, --check (un/formatted), -w idempotence, unknown-command.
|
||||||
- Safety invariants #2 (semantic equivalence), #3 (idempotence), #4 (graceful degradation)
|
- Safety invariants #2 (semantic equivalence), #3 (idempotence), #4 (graceful degradation)
|
||||||
are tested in `pkg/format` over the corpus.
|
are tested in `pkg/format` over the corpus.
|
||||||
|
- **Runtime safety gate** (`format.VerifySafe`, `pkg/format/safety.go`): every frontend runs
|
||||||
|
it before emitting. Bundles `SemanticallyEqual` (code token stream) + `CommentsPreserved`
|
||||||
|
(no comment dropped/merged/split/reworded, recursing into bodies) + `StructurallyBalanced`
|
||||||
|
(`()[]` / BEGIN·CASE·IF·LOOP…END profile, ignoring comment & string contents) + an
|
||||||
|
idempotence re-format. On failure the CLI prints the reason and keeps the original.
|
||||||
|
Caught two real bugs: multi-line `/* */` bodies were reindented as code, and col-0 `--`
|
||||||
|
lines were glued onto the previous line (merging consecutive comments) — both fixed in
|
||||||
|
`formatBodyStatements`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -213,7 +221,7 @@ as a proxy (new_line for content, inline for single-arg subexpressions).
|
|||||||
|
|
||||||
### ✅ Formatter — routine param alignment (`pkg/format/format.go`)
|
### ✅ Formatter — routine param alignment (`pkg/format/format.go`)
|
||||||
|
|
||||||
- `align_param_types`: `alignParamTypes()` pads param names so type columns align; default `true`.
|
- `align_param_types`: `alignParamTypes()` pads param names so type columns align; default `false` (house style: no type-column alignment in param lists).
|
||||||
- `routine_as_wrap`: when `false`, AS stays on the same line as the last option clause.
|
- `routine_as_wrap`: when `false`, AS stays on the same line as the last option clause.
|
||||||
- Golden file `testdata/corpus/test_a.pgsql` updated to reflect aligned params.
|
- Golden file `testdata/corpus/test_a.pgsql` updated to reflect aligned params.
|
||||||
|
|
||||||
@@ -221,7 +229,9 @@ as a proxy (new_line for content, inline for single-arg subexpressions).
|
|||||||
|
|
||||||
- `plpgsql_max_blank_lines`: blank-line runs capped at the configured limit; default `1`.
|
- `plpgsql_max_blank_lines`: blank-line runs capped at the configured limit; default `1`.
|
||||||
- `plpgsql_declare_align_type` + `plpgsql_declare_align_eq`: two-pass declare formatter
|
- `plpgsql_declare_align_type` + `plpgsql_declare_align_eq`: two-pass declare formatter
|
||||||
measures name/type widths then pads for alignment; `writeDeclareAligned` helper.
|
measures name/type widths then pads for alignment; `writeDeclareAligned` helper. Both
|
||||||
|
default `true` (house style). The `=` column is padded only to the widest type among
|
||||||
|
declarations that actually carry an assignment, so a lone `x text = '…';` stays tight.
|
||||||
- `plpgsql_if_then_newline`: when `false`, `joinThenToCondition` merges THEN onto the
|
- `plpgsql_if_then_newline`: when `false`, `joinThenToCondition` merges THEN onto the
|
||||||
preceding condition line.
|
preceding condition line.
|
||||||
- `plpgsql_loop_collapse`: `tryCollapseLoop` detects empty FOR/WHILE loop bodies and
|
- `plpgsql_loop_collapse`: `tryCollapseLoop` detects empty FOR/WHILE loop bodies and
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# Maintainer: Hein (Warky Devs) <hein@warky.dev>
|
# Maintainer: Hein (Warky Devs) <hein@warky.dev>
|
||||||
pkgname=pgtidy-bin
|
pkgname=pgtidy-bin
|
||||||
pkgver=0.0.7
|
pkgver=0.0.8
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="PostgreSQL SQL formatter and linter"
|
pkgdesc="PostgreSQL SQL formatter and linter"
|
||||||
arch=('x86_64' 'aarch64')
|
arch=('x86_64' 'aarch64')
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
Name: pgtidy
|
Name: pgtidy
|
||||||
Version: 0.0.7
|
Version: 0.0.8
|
||||||
Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
Summary: PostgreSQL SQL formatter and linter
|
Summary: PostgreSQL SQL formatter and linter
|
||||||
|
|
||||||
|
|||||||
@@ -130,12 +130,12 @@ func Default() Style {
|
|||||||
|
|
||||||
InsertCollapseValues: true,
|
InsertCollapseValues: true,
|
||||||
|
|
||||||
AlignParamTypes: true,
|
AlignParamTypes: false,
|
||||||
RoutineAsWrap: true,
|
RoutineAsWrap: true,
|
||||||
|
|
||||||
PlpgsqlMaxBlankLines: 1,
|
PlpgsqlMaxBlankLines: 1,
|
||||||
PlpgsqlDeclareAlignType: false,
|
PlpgsqlDeclareAlignType: true,
|
||||||
PlpgsqlDeclareAlignEq: false,
|
PlpgsqlDeclareAlignEq: true,
|
||||||
PlpgsqlIfThenNewline: true,
|
PlpgsqlIfThenNewline: true,
|
||||||
PlpgsqlLoopCollapse: true,
|
PlpgsqlLoopCollapse: true,
|
||||||
|
|
||||||
|
|||||||
+84
-7
@@ -177,7 +177,10 @@ func formatDeclareVars(b *strings.Builder, toks []cst.Tok, st config.Style) {
|
|||||||
if nw > nameColW {
|
if nw > nameColW {
|
||||||
nameColW = nw
|
nameColW = nw
|
||||||
}
|
}
|
||||||
if tw > typeColW {
|
// typeColW drives the '='/':='/DEFAULT column (align_eq only), so
|
||||||
|
// only declarations that actually carry an assignment participate —
|
||||||
|
// a bare "name type;" must not widen it.
|
||||||
|
if tw > typeColW && declHasAssignment(body) {
|
||||||
typeColW = tw
|
typeColW = tw
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,7 +210,7 @@ func formatDeclareVars(b *strings.Builder, toks []cst.Tok, st config.Style) {
|
|||||||
writeDeclareAligned(b, body, st, nameColW, typeColW)
|
writeDeclareAligned(b, body, st, nameColW, typeColW)
|
||||||
} else {
|
} else {
|
||||||
for j, t := range body {
|
for j, t := range body {
|
||||||
if j > 0 && needSpace(body[j-1].Tok, t.Tok) {
|
if j > 0 && needSpace(body[j-1].Tok, t.Tok) && !isPctTypeBoundary(body, j) {
|
||||||
b.WriteByte(' ')
|
b.WriteByte(' ')
|
||||||
}
|
}
|
||||||
b.WriteString(caseText(t.Tok, st))
|
b.WriteString(caseText(t.Tok, st))
|
||||||
@@ -220,6 +223,32 @@ func formatDeclareVars(b *strings.Builder, toks []cst.Tok, st config.Style) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// declHasAssignment reports whether a DECLARE variable body (name type … ) has
|
||||||
|
// a default assignment ( := / = / DEFAULT ) at paren depth 0.
|
||||||
|
func declHasAssignment(body []cst.Tok) bool {
|
||||||
|
depth := 0
|
||||||
|
for _, t := range body {
|
||||||
|
switch t.Tok.Kind {
|
||||||
|
case lexer.LParen, lexer.LBracket:
|
||||||
|
depth++
|
||||||
|
case lexer.RParen, lexer.RBracket:
|
||||||
|
if depth > 0 {
|
||||||
|
depth--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if depth != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if t.Tok.Kind == lexer.Operator && (t.Tok.Text == ":=" || t.Tok.Text == "=") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if t.Tok.Kind == lexer.Ident && lowerASCII(t.Tok.Text) == "default" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// declareNameTypeWidth returns the rendered width of the name and type portions
|
// declareNameTypeWidth returns the rendered width of the name and type portions
|
||||||
// of a DECLARE variable declaration (without the default assignment).
|
// of a DECLARE variable declaration (without the default assignment).
|
||||||
// Format is: [name type [:= default]] or [name type [DEFAULT default]].
|
// Format is: [name type [:= default]] or [name type [DEFAULT default]].
|
||||||
@@ -256,7 +285,7 @@ func declareNameTypeWidth(body []cst.Tok, st config.Style) (nameW, typeW int) {
|
|||||||
}
|
}
|
||||||
var tb strings.Builder
|
var tb strings.Builder
|
||||||
for j, t := range typeTokens {
|
for j, t := range typeTokens {
|
||||||
if j > 0 && needSpace(typeTokens[j-1].Tok, t.Tok) {
|
if j > 0 && needSpace(typeTokens[j-1].Tok, t.Tok) && !isPctTypeBoundary(typeTokens, j) {
|
||||||
tb.WriteByte(' ')
|
tb.WriteByte(' ')
|
||||||
}
|
}
|
||||||
tb.WriteString(caseText(t.Tok, st))
|
tb.WriteString(caseText(t.Tok, st))
|
||||||
@@ -315,7 +344,7 @@ func writeDeclareAligned(b *strings.Builder, body []cst.Tok, st config.Style, na
|
|||||||
|
|
||||||
var typeStr strings.Builder
|
var typeStr strings.Builder
|
||||||
for j, t := range typeTokens {
|
for j, t := range typeTokens {
|
||||||
if j > 0 && needSpace(typeTokens[j-1].Tok, t.Tok) {
|
if j > 0 && needSpace(typeTokens[j-1].Tok, t.Tok) && !isPctTypeBoundary(typeTokens, j) {
|
||||||
typeStr.WriteByte(' ')
|
typeStr.WriteByte(' ')
|
||||||
}
|
}
|
||||||
typeStr.WriteString(caseText(t.Tok, st))
|
typeStr.WriteString(caseText(t.Tok, st))
|
||||||
@@ -369,6 +398,22 @@ func formatBodyStatements(text string, st config.Style) string {
|
|||||||
normalised := strings.ReplaceAll(text, "\r\n", "\n")
|
normalised := strings.ReplaceAll(text, "\r\n", "\n")
|
||||||
rawLines := strings.Split(normalised, "\n")
|
rawLines := strings.Split(normalised, "\n")
|
||||||
|
|
||||||
|
// Mark the continuation lines of every multi-line /* … */ block comment.
|
||||||
|
// Those lines are comment content, not code: they must be carried verbatim
|
||||||
|
// with the comment's opening line, never split off and reindented as if
|
||||||
|
// they were statements of their own.
|
||||||
|
inBlockComment := make([]bool, len(rawLines))
|
||||||
|
for _, t := range lexer.Lex(normalised) {
|
||||||
|
if t.Kind != lexer.BlockComment {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
n := strings.Count(t.Text, "\n")
|
||||||
|
start := t.Line - 1 // lexer Line is 1-based within normalised
|
||||||
|
for k := 1; k <= n && start+k < len(inBlockComment); k++ {
|
||||||
|
inBlockComment[start+k] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
maxBlanks := st.PlpgsqlMaxBlankLines
|
maxBlanks := st.PlpgsqlMaxBlankLines
|
||||||
if maxBlanks < 0 {
|
if maxBlanks < 0 {
|
||||||
maxBlanks = 0
|
maxBlanks = 0
|
||||||
@@ -429,8 +474,13 @@ func formatBodyStatements(text string, st config.Style) string {
|
|||||||
}
|
}
|
||||||
case "exception":
|
case "exception":
|
||||||
inException = true
|
inException = true
|
||||||
|
// EXCEPTION belongs to its nearest enclosing BEGIN, so align it one
|
||||||
|
// level in from the current block body (col 0 for the outermost).
|
||||||
|
effectiveDepth = blockDepth - 1
|
||||||
|
if effectiveDepth < 0 {
|
||||||
effectiveDepth = 0
|
effectiveDepth = 0
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
baseIndent := strings.Repeat(st.Indent, effectiveDepth)
|
baseIndent := strings.Repeat(st.Indent, effectiveDepth)
|
||||||
|
|
||||||
@@ -454,8 +504,21 @@ func formatBodyStatements(text string, st config.Style) string {
|
|||||||
stmt = nil
|
stmt = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rawLine := range rawLines {
|
for j, rawLine := range rawLines {
|
||||||
line := strings.TrimRight(rawLine, "\r")
|
line := strings.TrimRight(rawLine, "\r")
|
||||||
|
|
||||||
|
if inBlockComment[j] {
|
||||||
|
// Verbatim continuation of a multi-line block comment: glue it to the
|
||||||
|
// bline holding the comment's opening line.
|
||||||
|
if len(stmt) > 0 {
|
||||||
|
last := &stmt[len(stmt)-1]
|
||||||
|
last.text += "\n" + line
|
||||||
|
} else {
|
||||||
|
stmt = append(stmt, bline{text: line})
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
indent := leadingWhitespace(line)
|
indent := leadingWhitespace(line)
|
||||||
stripped := line[len(indent):]
|
stripped := line[len(indent):]
|
||||||
|
|
||||||
@@ -468,6 +531,12 @@ func formatBodyStatements(text string, st config.Style) string {
|
|||||||
fw := lowerASCII(firstBodyKeyword(stripped))
|
fw := lowerASCII(firstBodyKeyword(stripped))
|
||||||
isColZero := indent == ""
|
isColZero := indent == ""
|
||||||
joinToPrev := isColZero && parenDepth == 0 && len(stmt) > 0 && !sqlClauseKw[fw]
|
joinToPrev := isColZero && parenDepth == 0 && len(stmt) > 0 && !sqlClauseKw[fw]
|
||||||
|
// A col-0 comment-only line is its own thing: never glue it onto the
|
||||||
|
// previous line — doing so buries a code line's trailing text in a
|
||||||
|
// comment and collapses consecutive -- comment lines into one.
|
||||||
|
if joinToPrev && len(significantBodyTokens(stripped)) == 0 {
|
||||||
|
joinToPrev = false
|
||||||
|
}
|
||||||
// Don't join a col-0 continuation to a comment-only preceding bline:
|
// Don't join a col-0 continuation to a comment-only preceding bline:
|
||||||
// the comment has no structural keyword so `continue ;` at col-0 would
|
// the comment has no structural keyword so `continue ;` at col-0 would
|
||||||
// disappear into the comment text and be invisible to the lexer.
|
// disappear into the comment text and be invisible to the lexer.
|
||||||
@@ -589,7 +658,13 @@ func formatBodyStmtLines(lines []bline, baseIndent string, st config.Style) []st
|
|||||||
for i, ll := range lines {
|
for i, ll := range lines {
|
||||||
text := ll.text
|
text := ll.text
|
||||||
indent := baseIndent
|
indent := baseIndent
|
||||||
if i > 0 && ll.indent != "" && !isStandaloneBodyKeyword(ll.text, "then", "else", "elsif", "elseif") {
|
switch {
|
||||||
|
case i > 0 && ll.indent == "" && len(significantBodyTokens(text)) == 0:
|
||||||
|
// A column-0 comment line trailing a multi-line (commented-out)
|
||||||
|
// statement is a continuation the author left flush-left — keep it
|
||||||
|
// there rather than re-indenting it to block depth.
|
||||||
|
indent = ""
|
||||||
|
case i > 0 && ll.indent != "" && !isStandaloneBodyKeyword(ll.text, "then", "else", "elsif", "elseif"):
|
||||||
indent = ll.indent
|
indent = ll.indent
|
||||||
}
|
}
|
||||||
out = append(out, indent+text)
|
out = append(out, indent+text)
|
||||||
@@ -639,7 +714,9 @@ func reindentBodyDML(lines []bline, baseIndent string, st config.Style) []string
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if lineDepth == 0 && (kw == "and" || kw == "or") {
|
if lineDepth == 0 && (kw == "and" || kw == "or") {
|
||||||
out = append(out, baseIndent+st.Indent+strings.TrimSpace(text))
|
// House style: AND/OR line up with the WHERE keyword; the first
|
||||||
|
// predicate is indented two levels under it.
|
||||||
|
out = append(out, baseIndent+strings.TrimSpace(text))
|
||||||
afterWhere = false
|
afterWhere = false
|
||||||
updateBodyParenDepth(text, &parenDepth)
|
updateBodyParenDepth(text, &parenDepth)
|
||||||
continue
|
continue
|
||||||
|
|||||||
+1
-1
@@ -462,7 +462,7 @@ func dmlInline(toks []cst.Tok, st config.Style) string {
|
|||||||
}
|
}
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
for i, t := range toks {
|
for i, t := range toks {
|
||||||
if i > 0 && needSpace(toks[i-1].Tok, t.Tok) {
|
if i > 0 && needSpace(toks[i-1].Tok, t.Tok) && !isPctTypeBoundary(toks, i) {
|
||||||
b.WriteByte(' ')
|
b.WriteByte(' ')
|
||||||
}
|
}
|
||||||
// Space after comma in calls: func(a, b) vs func(a,b).
|
// Space after comma in calls: func(a, b) vs func(a,b).
|
||||||
|
|||||||
+30
-4
@@ -147,8 +147,9 @@ func (p *printer) writeCreateFunction(cf *cst.CreateFunction) {
|
|||||||
paramTexts = alignParamTypes(paramTexts)
|
paramTexts = alignParamTypes(paramTexts)
|
||||||
}
|
}
|
||||||
|
|
||||||
first := p.st.Indent + " " // align item text one column past the comma
|
// House style: first parameter indented one level; leading-comma
|
||||||
cont := p.st.Indent
|
// continuation lines carry the comma at column 0 followed by one space.
|
||||||
|
first := p.st.Indent
|
||||||
for i, text := range paramTexts {
|
for i, text := range paramTexts {
|
||||||
param := cf.Params[i]
|
param := cf.Params[i]
|
||||||
p.nl()
|
p.nl()
|
||||||
@@ -159,7 +160,6 @@ func (p *printer) writeCreateFunction(cf *cst.CreateFunction) {
|
|||||||
p.b.WriteString(",")
|
p.b.WriteString(",")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
p.b.WriteString(cont)
|
|
||||||
p.b.WriteString(", ")
|
p.b.WriteString(", ")
|
||||||
p.b.WriteString(text)
|
p.b.WriteString(text)
|
||||||
}
|
}
|
||||||
@@ -179,6 +179,7 @@ func (p *printer) writeCreateFunction(cf *cst.CreateFunction) {
|
|||||||
|
|
||||||
for _, clause := range cf.Options {
|
for _, clause := range cf.Options {
|
||||||
p.nl()
|
p.nl()
|
||||||
|
p.b.WriteString(p.st.Indent)
|
||||||
p.b.WriteString(p.inline(clause))
|
p.b.WriteString(p.inline(clause))
|
||||||
}
|
}
|
||||||
if cf.As != nil {
|
if cf.As != nil {
|
||||||
@@ -218,7 +219,7 @@ func (p *printer) inline(toks []cst.Tok) string {
|
|||||||
}
|
}
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
for i, t := range toks {
|
for i, t := range toks {
|
||||||
if i > 0 && needSpace(toks[i-1].Tok, t.Tok) {
|
if i > 0 && needSpace(toks[i-1].Tok, t.Tok) && !isPctTypeBoundary(toks, i) {
|
||||||
b.WriteByte(' ')
|
b.WriteByte(' ')
|
||||||
}
|
}
|
||||||
var prev lexer.Token
|
var prev lexer.Token
|
||||||
@@ -254,6 +255,31 @@ func (p *printer) trailingComments(lead cst.Trivia) {
|
|||||||
// tightOps are operators printed without surrounding spaces.
|
// tightOps are operators printed without surrounding spaces.
|
||||||
var tightOps = map[string]bool{"::": true, ":": true, "->": true, "->>": true}
|
var tightOps = map[string]bool{"::": true, ":": true, "->": true, "->>": true}
|
||||||
|
|
||||||
|
// isPctTypeBoundary reports whether the gap between toks[i-1] and toks[i] sits
|
||||||
|
// inside a %TYPE / %ROWTYPE modifier (e.g. core.tbl%rowtype), which is printed
|
||||||
|
// tight like "::" rather than as the modulo operator.
|
||||||
|
func isPctTypeBoundary(toks []cst.Tok, i int) bool {
|
||||||
|
if i <= 0 || i >= len(toks) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
isPct := func(t lexer.Token) bool { return t.Kind == lexer.Operator && t.Text == "%" }
|
||||||
|
isTypeWord := func(t lexer.Token) bool {
|
||||||
|
if t.Kind != lexer.Ident {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
l := lowerASCII(t.Text)
|
||||||
|
return l == "type" || l == "rowtype"
|
||||||
|
}
|
||||||
|
prev, cur := toks[i-1].Tok, toks[i].Tok
|
||||||
|
if isPct(prev) && isTypeWord(cur) {
|
||||||
|
return true // space after %
|
||||||
|
}
|
||||||
|
if isPct(cur) && i+1 < len(toks) && isTypeWord(toks[i+1].Tok) {
|
||||||
|
return true // space before %
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// parenKws are keywords that always take a space before '(' because they
|
// parenKws are keywords that always take a space before '(' because they
|
||||||
// introduce a subquery or a bracketed clause, not a function-call argument list.
|
// introduce a subquery or a bracketed clause, not a function-call argument list.
|
||||||
var parenKws = map[string]bool{
|
var parenKws = map[string]bool{
|
||||||
|
|||||||
@@ -184,18 +184,16 @@ func TestCorpusIdempotentAndSafe(t *testing.T) {
|
|||||||
}
|
}
|
||||||
src := string(data)
|
src := string(data)
|
||||||
once := format(src)
|
once := format(src)
|
||||||
twice := format(once)
|
// VerifySafe bundles every runtime gate: semantic equivalence, comment
|
||||||
if once != twice {
|
// preservation, structural balance, and idempotence.
|
||||||
t.Errorf("%s: not idempotent", e.Name())
|
if err := VerifySafe(src, once, config.Default()); err != nil {
|
||||||
}
|
t.Errorf("%s: %v", e.Name(), err)
|
||||||
if !semanticallyEqual(src, once) {
|
|
||||||
t.Errorf("%s: formatting changed semantics", e.Name())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if seen == 0 {
|
if seen == 0 {
|
||||||
t.Skip("no corpus files")
|
t.Skip("no corpus files")
|
||||||
}
|
}
|
||||||
t.Logf("formatted %d corpus files (idempotent + semantically equal)", seen)
|
t.Logf("verified %d corpus files (semantic + comments + structure + idempotence)", seen)
|
||||||
}
|
}
|
||||||
|
|
||||||
// semanticallyEqual is a test-local alias for the exported safety check.
|
// semanticallyEqual is a test-local alias for the exported safety check.
|
||||||
|
|||||||
+201
-3
@@ -1,11 +1,52 @@
|
|||||||
package format
|
package format
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"git.warky.dev/wdevs/pgtidy/pkg/config"
|
||||||
"git.warky.dev/wdevs/pgtidy/pkg/lexer"
|
"git.warky.dev/wdevs/pgtidy/pkg/lexer"
|
||||||
|
"git.warky.dev/wdevs/pgtidy/pkg/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// VerifySafe runs every safety invariant against a formatting result before it
|
||||||
|
// is written to disk or returned to an editor. src is the original input, out
|
||||||
|
// the formatted output, and st the style out was produced with. It returns nil
|
||||||
|
// when out is safe to emit, otherwise an error naming the invariant that failed.
|
||||||
|
//
|
||||||
|
// The checks, in order of cost:
|
||||||
|
//
|
||||||
|
// 1. Semantic equivalence — the non-trivia (code) token stream is unchanged:
|
||||||
|
// identifiers/keywords compare case-insensitively, everything else exactly,
|
||||||
|
// recursing into dollar-quoted bodies. Comments and whitespace are trivia
|
||||||
|
// and are deliberately ignored here.
|
||||||
|
// 2. Comment preservation — every -- and /* */ comment in src reappears in out,
|
||||||
|
// in the same order, with the same content (ignoring only trailing
|
||||||
|
// whitespace and CRLF/LF). The formatter may move or re-indent a comment but
|
||||||
|
// must never drop, merge, split, or reword one.
|
||||||
|
// 3. Structural balance — the ( ) [ ] and BEGIN/CASE/IF/LOOP…END nesting
|
||||||
|
// profile of out matches src's, counting only real code tokens (anything
|
||||||
|
// inside a comment or a string/dollar-quoted literal is ignored).
|
||||||
|
// 4. Idempotence — formatting out again yields out unchanged.
|
||||||
|
//
|
||||||
|
// Any failure means the formatter has a bug: the caller must keep the original
|
||||||
|
// source and never emit out.
|
||||||
|
func VerifySafe(src, out string, st config.Style) error {
|
||||||
|
if !SemanticallyEqual(src, out) {
|
||||||
|
return fmt.Errorf("code token stream changed")
|
||||||
|
}
|
||||||
|
if err := CommentsPreserved(src, out); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := StructurallyBalanced(src, out); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if reformatted := File(parser.Parse(out), st); reformatted != out {
|
||||||
|
return fmt.Errorf("output is not idempotent (a second format pass would change it)")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// SemanticallyEqual reports whether a and b have the same non-trivia token
|
// SemanticallyEqual reports whether a and b have the same non-trivia token
|
||||||
// stream, i.e. formatting may only ever change whitespace/comment trivia and
|
// stream, i.e. formatting may only ever change whitespace/comment trivia and
|
||||||
// layout — it must never add, remove, or alter a token of actual code.
|
// layout — it must never add, remove, or alter a token of actual code.
|
||||||
@@ -14,9 +55,9 @@ import (
|
|||||||
// must match exactly. Dollar-quoted body tokens are compared recursively so
|
// must match exactly. Dollar-quoted body tokens are compared recursively so
|
||||||
// that independent body reformatting doesn't trigger a false failure.
|
// that independent body reformatting doesn't trigger a false failure.
|
||||||
//
|
//
|
||||||
// The CLI and LSP must call this before ever writing or emitting formatted
|
// The CLI and LSP must call this (via VerifySafe) before ever writing or
|
||||||
// output: if it returns false, the formatter has a bug and the original
|
// emitting formatted output: if it returns false, the formatter has a bug and
|
||||||
// source must be kept, never the (corrupting) formatted output.
|
// the original source must be kept, never the (corrupting) formatted output.
|
||||||
func SemanticallyEqual(a, b string) bool {
|
func SemanticallyEqual(a, b string) bool {
|
||||||
ta := significantTokens(a)
|
ta := significantTokens(a)
|
||||||
tb := significantTokens(b)
|
tb := significantTokens(b)
|
||||||
@@ -32,6 +73,14 @@ func SemanticallyEqual(a, b string) bool {
|
|||||||
if !strings.EqualFold(ta[i].Text, tb[i].Text) {
|
if !strings.EqualFold(ta[i].Text, tb[i].Text) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
case lexer.String, lexer.EscapeString, lexer.BitString, lexer.HexString, lexer.UnicodeString:
|
||||||
|
// A CRLF vs LF difference inside a multi-line string literal is a
|
||||||
|
// line-ending normalisation, not a change of code content — the
|
||||||
|
// formatter always re-emits layout with st.Newline. Compare the
|
||||||
|
// literal modulo \r\n ↔ \n.
|
||||||
|
if normNL(ta[i].Text) != normNL(tb[i].Text) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
case lexer.DollarString:
|
case lexer.DollarString:
|
||||||
_, innerA, _, okA := splitDollarQuote(ta[i].Text)
|
_, innerA, _, okA := splitDollarQuote(ta[i].Text)
|
||||||
_, innerB, _, okB := splitDollarQuote(tb[i].Text)
|
_, innerB, _, okB := splitDollarQuote(tb[i].Text)
|
||||||
@@ -47,6 +96,155 @@ func SemanticallyEqual(a, b string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CommentsPreserved reports whether every comment in a survives into b with its
|
||||||
|
// text intact. Comments are compared in document order; each is reduced to its
|
||||||
|
// sequence of non-blank text lines (line endings normalised, every line trimmed
|
||||||
|
// of surrounding whitespace, blank lines dropped) so that the formatter is free
|
||||||
|
// to move or re-indent a comment but can never drop, merge, split, reorder, or
|
||||||
|
// reword one. Comments inside dollar-quoted bodies are included (the bodies are
|
||||||
|
// lexed recursively). A non-nil error describes the first divergence.
|
||||||
|
func CommentsPreserved(a, b string) error {
|
||||||
|
ca := comments(a)
|
||||||
|
cb := comments(b)
|
||||||
|
if len(ca) != len(cb) {
|
||||||
|
return fmt.Errorf("comment count changed: input has %d, output has %d", len(ca), len(cb))
|
||||||
|
}
|
||||||
|
for i := range ca {
|
||||||
|
if ca[i] != cb[i] {
|
||||||
|
return fmt.Errorf("comment %d/%d changed:\n input: %q\n output: %q", i+1, len(ca), ca[i], cb[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// comments returns the normalised text of every -- and /* */ comment in src, in
|
||||||
|
// order, descending into dollar-quoted bodies.
|
||||||
|
func comments(src string) []string {
|
||||||
|
var out []string
|
||||||
|
for _, t := range lexer.Lex(src) {
|
||||||
|
switch t.Kind {
|
||||||
|
case lexer.LineComment, lexer.BlockComment:
|
||||||
|
out = append(out, normComment(t.Text))
|
||||||
|
case lexer.DollarString:
|
||||||
|
if _, inner, _, ok := splitDollarQuote(t.Text); ok {
|
||||||
|
out = append(out, comments(inner)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// normComment canonicalises a comment token to its content — the ordered list of
|
||||||
|
// non-blank text lines, each stripped of surrounding whitespace, joined with LF.
|
||||||
|
// Line endings and indentation are layout, not content, so they are discarded;
|
||||||
|
// dropping or rewording an actual line of comment text still shows up.
|
||||||
|
func normComment(s string) string {
|
||||||
|
s = strings.ReplaceAll(s, "\r\n", "\n")
|
||||||
|
s = strings.ReplaceAll(s, "\r", "\n")
|
||||||
|
var lines []string
|
||||||
|
for _, ln := range strings.Split(s, "\n") {
|
||||||
|
if ln = strings.TrimSpace(ln); ln != "" {
|
||||||
|
lines = append(lines, ln)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(lines, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// StructurallyBalanced reports whether a and b have the same delimiter and block
|
||||||
|
// nesting profile: identical counts of ( ) [ ] and of the PL/pgSQL block
|
||||||
|
// keywords BEGIN / CASE / IF / LOOP / END (and the compound END IF / END LOOP /
|
||||||
|
// END CASE), plus an identical running paren/bracket depth trace. Only real code
|
||||||
|
// tokens are counted — anything inside a -- or /* */ comment is trivia and is
|
||||||
|
// skipped, and string / dollar-quoted literals are opaque single tokens whose
|
||||||
|
// contents never register (dollar-quoted bodies are recursed into separately).
|
||||||
|
//
|
||||||
|
// Given SemanticallyEqual, this is defence in depth: an independent re-count
|
||||||
|
// with different code that catches a structural token slipping through a bug in
|
||||||
|
// the token-stream comparison (e.g. its dollar-quote or CRLF handling), and it
|
||||||
|
// pins down *where* the structure broke.
|
||||||
|
func StructurallyBalanced(a, b string) error {
|
||||||
|
pa := structureProfile(a)
|
||||||
|
pb := structureProfile(b)
|
||||||
|
if pa.parenDepthTrace != pb.parenDepthTrace {
|
||||||
|
return fmt.Errorf("parenthesis/bracket nesting changed")
|
||||||
|
}
|
||||||
|
for _, k := range structureKeys {
|
||||||
|
if pa.counts[k] != pb.counts[k] {
|
||||||
|
return fmt.Errorf("structural token %q count changed: input %d, output %d", k, pa.counts[k], pb.counts[k])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var structureKeys = []string{"(", ")", "[", "]", "begin", "case", "if", "loop", "end", "end if", "end loop", "end case"}
|
||||||
|
|
||||||
|
type structProfile struct {
|
||||||
|
counts map[string]int
|
||||||
|
// parenDepthTrace is the sequence of running ( ) [ ] depths after each
|
||||||
|
// bracket token, joined with commas — a compact fingerprint of the nesting
|
||||||
|
// shape that diverges as soon as an open/close is added, dropped, or moved.
|
||||||
|
parenDepthTrace string
|
||||||
|
}
|
||||||
|
|
||||||
|
func structureProfile(src string) structProfile {
|
||||||
|
p := structProfile{counts: map[string]int{}}
|
||||||
|
var trace strings.Builder
|
||||||
|
depth := 0
|
||||||
|
toks := significantTokens(src) // trivia (comments/whitespace) already excluded
|
||||||
|
for i := 0; i < len(toks); i++ {
|
||||||
|
t := toks[i]
|
||||||
|
switch t.Kind {
|
||||||
|
case lexer.LParen:
|
||||||
|
p.counts["("]++
|
||||||
|
depth++
|
||||||
|
fmt.Fprintf(&trace, "%d,", depth)
|
||||||
|
case lexer.RParen:
|
||||||
|
p.counts[")"]++
|
||||||
|
depth--
|
||||||
|
fmt.Fprintf(&trace, "%d,", depth)
|
||||||
|
case lexer.LBracket:
|
||||||
|
p.counts["["]++
|
||||||
|
depth++
|
||||||
|
fmt.Fprintf(&trace, "%d,", depth)
|
||||||
|
case lexer.RBracket:
|
||||||
|
p.counts["]"]++
|
||||||
|
depth--
|
||||||
|
fmt.Fprintf(&trace, "%d,", depth)
|
||||||
|
case lexer.Ident:
|
||||||
|
switch lowerASCII(t.Text) {
|
||||||
|
case "begin", "case", "if", "loop":
|
||||||
|
p.counts[lowerASCII(t.Text)]++
|
||||||
|
case "end":
|
||||||
|
p.counts["end"]++
|
||||||
|
if i+1 < len(toks) && toks[i+1].Kind == lexer.Ident {
|
||||||
|
switch lowerASCII(toks[i+1].Text) {
|
||||||
|
case "if":
|
||||||
|
p.counts["end if"]++
|
||||||
|
case "loop":
|
||||||
|
p.counts["end loop"]++
|
||||||
|
case "case":
|
||||||
|
p.counts["end case"]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case lexer.DollarString:
|
||||||
|
if _, inner, _, ok := splitDollarQuote(t.Text); ok {
|
||||||
|
sub := structureProfile(inner)
|
||||||
|
for _, k := range structureKeys {
|
||||||
|
p.counts[k] += sub.counts[k]
|
||||||
|
}
|
||||||
|
trace.WriteString("[" + sub.parenDepthTrace + "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.parenDepthTrace = trace.String()
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// normNL collapses CRLF to LF so string literals compare independent of the
|
||||||
|
// source file's line-ending convention.
|
||||||
|
func normNL(s string) string { return strings.ReplaceAll(s, "\r\n", "\n") }
|
||||||
|
|
||||||
// significantTokens lexes src and returns its tokens excluding EOF and trivia
|
// significantTokens lexes src and returns its tokens excluding EOF and trivia
|
||||||
// (whitespace/comments).
|
// (whitespace/comments).
|
||||||
func significantTokens(src string) []lexer.Token {
|
func significantTokens(src string) []lexer.Token {
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package format
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.warky.dev/wdevs/pgtidy/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCommentsPreserved(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
a, b string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"identical", "select 1; -- note", "select 1;\n-- note", false},
|
||||||
|
{"reindented block comment", "/* a\n b */ select 1", " /* a\nb */\nselect 1", false},
|
||||||
|
{"crlf line comment", "-- note\r\nselect 1", "-- note\nselect 1", false},
|
||||||
|
{"dropped comment", "select 1; -- keep me\nselect 2;", "select 1;\nselect 2;", true},
|
||||||
|
{"merged comments", "-- one\n-- two\nselect 1", "-- one -- two\nselect 1", true},
|
||||||
|
{"reworded comment", "-- alpha\nselect 1", "-- beta\nselect 1", true},
|
||||||
|
{"comment inside body preserved", // -- inside a dollar-quoted body
|
||||||
|
"do $$ begin\n-- inner\nperform 1;\nend $$;",
|
||||||
|
"DO\n$$\nbegin\n -- inner\n perform 1;\nend\n$$;", false},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
err := CommentsPreserved(c.a, c.b)
|
||||||
|
if (err != nil) != c.wantErr {
|
||||||
|
t.Fatalf("CommentsPreserved(%q, %q) err = %v, wantErr %v", c.a, c.b, err, c.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommentsPreservedIgnoresCodeText(t *testing.T) {
|
||||||
|
// A ( ; keyword etc. inside a comment must not be read as code by the check.
|
||||||
|
a := "select 1; -- ( begin case end ) ;\nselect 2;"
|
||||||
|
b := "select 1;\nselect 2;\n-- ( begin case end ) ;"
|
||||||
|
if err := CommentsPreserved(a, b); err != nil {
|
||||||
|
t.Fatalf("comment content that looks like code tripped the check: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructurallyBalanced(t *testing.T) {
|
||||||
|
if err := StructurallyBalanced("select f((a+b)*c) from t", "select f( ( a + b ) * c )\nfrom t"); err != nil {
|
||||||
|
t.Errorf("whitespace-only reformat flagged: %v", err)
|
||||||
|
}
|
||||||
|
// Delimiters that live inside a comment or a string must not count.
|
||||||
|
if err := StructurallyBalanced("select ')(' as x -- ((((\nfrom t", "select ')(' as x\n-- ((((\nfrom t"); err != nil {
|
||||||
|
t.Errorf("comment/string delimiters counted: %v", err)
|
||||||
|
}
|
||||||
|
if err := StructurallyBalanced("select (a) from t", "select (a from t"); err == nil {
|
||||||
|
t.Errorf("dropped ')' not detected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifySafeCatchesNonIdempotent(t *testing.T) {
|
||||||
|
src := "create function f() returns void language sql as $$ select 1 $$;"
|
||||||
|
out := format(src)
|
||||||
|
if err := VerifySafe(src, out, config.Default()); err != nil {
|
||||||
|
t.Fatalf("clean format rejected: %v", err)
|
||||||
|
}
|
||||||
|
// A hand-mangled "output" that differs from what the formatter would produce
|
||||||
|
// must be rejected (idempotence gate).
|
||||||
|
if err := VerifySafe(src, out+"\n\n\n", config.Default()); err == nil {
|
||||||
|
t.Errorf("non-idempotent output accepted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifySafeBlockCommentInBody(t *testing.T) {
|
||||||
|
// Regression: a multi-line /* */ comment inside a PL/pgSQL body was being
|
||||||
|
// re-split and reindented as if its lines were statements.
|
||||||
|
src := "CREATE FUNCTION f() RETURNS void LANGUAGE plpgsql AS $$\n" +
|
||||||
|
"BEGIN\n" +
|
||||||
|
" /*\n" +
|
||||||
|
" update t u\n" +
|
||||||
|
" set x = 1\n" +
|
||||||
|
" where u.id = 2\n" +
|
||||||
|
" and u.y = 3;\n" +
|
||||||
|
" */\n" +
|
||||||
|
" perform 1;\n" +
|
||||||
|
"END $$;\n"
|
||||||
|
out := format(src)
|
||||||
|
if err := VerifySafe(src, out, config.Default()); err != nil {
|
||||||
|
t.Fatalf("block comment in body mangled: %v", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(out, "where u.id = 2") {
|
||||||
|
t.Errorf("block comment interior lost a line:\n%s", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -124,7 +124,7 @@ func (s *server) handle(raw []byte) bool {
|
|||||||
s.reply(req.ID, []textEdit{})
|
s.reply(req.ID, []textEdit{})
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !format.SemanticallyEqual(text, formatted) {
|
if err := format.VerifySafe(text, formatted, s.cfg); err != nil {
|
||||||
s.reply(req.ID, []textEdit{})
|
s.reply(req.ID, []textEdit{})
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -228,7 +228,7 @@ func (s *server) rangeFormat(text string, r lspRange) []textEdit {
|
|||||||
if formatted == text {
|
if formatted == text {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if !format.SemanticallyEqual(text, formatted) {
|
if err := format.VerifySafe(text, formatted, s.cfg); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+797
@@ -0,0 +1,797 @@
|
|||||||
|
--select * from dropall('action_init','core');
|
||||||
|
CREATE OR REPLACE FUNCTION core.action_init(
|
||||||
|
p_table text
|
||||||
|
, p_rid integer
|
||||||
|
, INOUT p_parms jsonb default json_build_object()
|
||||||
|
, OUT p_retval integer
|
||||||
|
, OUT p_errmsg text
|
||||||
|
)
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
VOLATILE
|
||||||
|
SECURITY DEFINER
|
||||||
|
AS
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
--Error Handling--
|
||||||
|
m_funcname text = 'core.action_init';
|
||||||
|
m_errmsg text;
|
||||||
|
m_errcontext text;
|
||||||
|
m_errdetail text;
|
||||||
|
m_errhint text;
|
||||||
|
m_errstate text;
|
||||||
|
m_retval integer;
|
||||||
|
--Error Handling--
|
||||||
|
--r_workflow core.workflowitem%rowtype;
|
||||||
|
r_tasklist core.tasklist%rowtype;
|
||||||
|
r_taskitem core.taskitem%rowtype;
|
||||||
|
r_taskitem_prev core.taskitem%rowtype;
|
||||||
|
r_taskitem_parent core.taskitem%rowtype;
|
||||||
|
r_mastertaskitem core.mastertaskitem%rowtype;
|
||||||
|
r_mastertaskitemevent core.mastertaskitemevent%rowtype;
|
||||||
|
r_taskitemevent core.taskitemevent%rowtype;
|
||||||
|
r_taskitemevent_parent core.taskitemevent%rowtype;
|
||||||
|
m_rid_hub_user integer;
|
||||||
|
m_rid_hub_payload integer;
|
||||||
|
m_temp_rid integer;
|
||||||
|
m_temp_rid_list integer[];
|
||||||
|
j_results jsonb;
|
||||||
|
BEGIN
|
||||||
|
p_retval = 0;
|
||||||
|
p_errmsg = '';
|
||||||
|
m_rid_hub_user = _bv(core.f_get_user_hub_rid(), (
|
||||||
|
select hub.rid_hub
|
||||||
|
from core.hub
|
||||||
|
where hub.hubtype = 'program'
|
||||||
|
limit 1
|
||||||
|
));
|
||||||
|
m_rid_hub_payload = _try_integers(p_parms ->> 'rid_hub', p_parms ->> 'rid_hub_payload');
|
||||||
|
|
||||||
|
if nv(p_rid) = 0
|
||||||
|
then
|
||||||
|
raise 'Invalid or no p_rid';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
---select * from meta.table_prefix t where t.tablename = 'mastertaskitemevent'
|
||||||
|
if p_table::citext in ('taskitemevent', 'tiv')
|
||||||
|
then
|
||||||
|
raise exception 'Action Init is not allowed on task item events. Please use the mastertaskitemevent or taskitem as entry point.';
|
||||||
|
|
||||||
|
elsif p_table::citext in ('mastertaskitemevent', 'mtev')
|
||||||
|
then
|
||||||
|
select *
|
||||||
|
from core.mastertaskitemevent mtev
|
||||||
|
where mtev.rid_mastertaskitemevent = p_rid
|
||||||
|
into r_mastertaskitemevent;
|
||||||
|
|
||||||
|
if r_mastertaskitemevent.inactive > 0
|
||||||
|
then
|
||||||
|
raise exception 'The template task item event is inactive. rid_mastertaskitemevent = %',r_mastertaskitemevent.rid_mastertaskitemevent;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
select ttie.*
|
||||||
|
from core.taskitemevent ttie
|
||||||
|
where ttie.rid_taskitemevent = _try_integer(p_parms ->> 'parent_rid_taskitemevent', 0)
|
||||||
|
into r_taskitemevent_parent;
|
||||||
|
|
||||||
|
-- try to get the given task item
|
||||||
|
select *
|
||||||
|
from core.taskitem ti
|
||||||
|
where
|
||||||
|
ti.rid_taskitem = _try_integer(p_parms ->> 'rid_taskitem', -1)
|
||||||
|
and ti.rid_mastertaskitem = r_mastertaskitemevent.rid_mastertaskitem
|
||||||
|
into r_taskitem;
|
||||||
|
|
||||||
|
if _try_integer(p_parms ->> 'parent_rid_taskitem', 0) > 0
|
||||||
|
then
|
||||||
|
select *
|
||||||
|
from core.taskitem ti
|
||||||
|
where ti.rid_taskitem = _try_integer(p_parms ->> 'parent_rid_taskitem', 0)
|
||||||
|
into r_taskitem_parent;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
select *
|
||||||
|
from core.tasklist tl
|
||||||
|
where tl.rid_tasklist = r_taskitem.rid_tasklist
|
||||||
|
into r_tasklist;
|
||||||
|
|
||||||
|
-- perform log_event(m_funcname,format(E'Action Init via %s=%s Item:%s(%s rid_taskitem:%s[%s] < %s %s[%s]) Event:%s(%s[%s],parent_rid_taskitemevent:%s[%s])
|
||||||
|
-- p_parms:%s',p_table,p_rid
|
||||||
|
-- , r_mastertaskitem.description, r_mastertaskitem.rid_mastertaskitem,r_taskitem.rid_taskitem
|
||||||
|
-- ,r_taskitem.status, r_taskitem_parent.description
|
||||||
|
-- ,r_taskitem_parent.rid_taskitem,r_taskitem_parent.status
|
||||||
|
-- , r_mastertaskitemevent.description,r_mastertaskitemevent.status, r_mastertaskitemevent.rid_mastertaskitemevent
|
||||||
|
-- , r_taskitemevent_parent.rid_taskitemevent, r_taskitemevent_parent.status
|
||||||
|
-- , p_parms),bt_enum('eventlog','local notice'));
|
||||||
|
|
||||||
|
-- raise 'p_rid=%=% r_taskitem.status =% r_taskitem_parent.status=%,% r_mastertaskitemevent.status=% pps=%'
|
||||||
|
-- ,p_table,p_rid, r_taskitem.status,r_taskitem_parent.description,r_taskitem_parent.status, r_mastertaskitemevent.status
|
||||||
|
-- ,(select ti.status
|
||||||
|
-- from core.taskitem ti
|
||||||
|
-- where ti.rid_taskitem = r_taskitem_parent.rid_taskitem_parentaction);
|
||||||
|
|
||||||
|
-- if r_mastertaskitemevent.status = core._enumi('eventstatus','complete')
|
||||||
|
-- and r_taskitem_parent.rid_taskitem > 0
|
||||||
|
-- and exists (
|
||||||
|
-- select 1
|
||||||
|
-- from core.taskitem ti
|
||||||
|
-- where ti.rid_taskitem = r_taskitem_parent.rid_taskitem_parentaction
|
||||||
|
-- and ti.status in (core._enumi('eventstatus','complete')
|
||||||
|
-- ,core._enumi('eventstatus','error')
|
||||||
|
-- ,core._enumi('eventstatus','retry'))
|
||||||
|
-- )
|
||||||
|
-- then
|
||||||
|
-- r_taskitem = null;
|
||||||
|
--
|
||||||
|
-- end if;
|
||||||
|
|
||||||
|
--If there is no action, try getting the same open one of this type for this hub
|
||||||
|
if nv(r_taskitem.rid_taskitem) = 0
|
||||||
|
then
|
||||||
|
--Create item linked to task for this hub if not found
|
||||||
|
|
||||||
|
select r.p_retval, r.p_errmsg, r.p_a_rid_taskitem, r.p_parameters
|
||||||
|
from core.taskitem_get_or_prime(_jsonb_object_cat(
|
||||||
|
_jsonb_object_cat(r_taskitem_parent.jsonvalue
|
||||||
|
, jsonb_build_object('AOP', null, 'controls', null, 'rid_taskitem', null, 'message', null, 'subject', null,
|
||||||
|
'alerttype', null)
|
||||||
|
)
|
||||||
|
, p_parms
|
||||||
|
, jsonb_build_object(
|
||||||
|
'rid_mastertaskitem', r_mastertaskitemevent.rid_mastertaskitem
|
||||||
|
, 'rid_mastertaskitemevent', r_mastertaskitemevent.rid_mastertaskitemevent
|
||||||
|
, 'parent_rid_taskitemevent', r_taskitemevent_parent.rid_taskitemevent
|
||||||
|
, 'parent_rid_taskitem', _bv(r_taskitem_parent.rid_taskitem
|
||||||
|
, _try_integer(p_parms ->> 'parent_rid_taskitem', 0)
|
||||||
|
)
|
||||||
|
, 'rid_tasklist_prev', _try_integers(p_parms ->> 'rid_tasklist_prev', p_parms ->> 'rid_tasklist'
|
||||||
|
, r_tasklist.rid_tasklist::text, r_taskitem.rid_tasklist::text)
|
||||||
|
, 'rid_taskitem_parentaction', p_parms -> 'rid_taskitem_parentaction'
|
||||||
|
--,'rid_tasklist', r_tasklist.rid_tasklist
|
||||||
|
)
|
||||||
|
)) r
|
||||||
|
into m_retval,m_errmsg,m_temp_rid_list,j_results;
|
||||||
|
|
||||||
|
if m_retval > 0
|
||||||
|
then
|
||||||
|
raise exception '%',m_errmsg using hint = 'in taskitem_get_or_prime 2';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
else
|
||||||
|
m_temp_rid_list = array [r_taskitem.rid_taskitem]::integer[];
|
||||||
|
end if;
|
||||||
|
|
||||||
|
|
||||||
|
p_parms = jsonb_build_object();
|
||||||
|
|
||||||
|
for m_temp_rid in (
|
||||||
|
select u.n
|
||||||
|
from unnest(m_temp_rid_list) u(n)
|
||||||
|
)
|
||||||
|
loop
|
||||||
|
select *
|
||||||
|
from core.taskitem ti
|
||||||
|
where ti.rid_taskitem = m_temp_rid
|
||||||
|
into r_taskitem;
|
||||||
|
|
||||||
|
select *
|
||||||
|
from core.tasklist tl
|
||||||
|
where tl.rid_tasklist = r_taskitem.rid_tasklist
|
||||||
|
into r_tasklist;
|
||||||
|
|
||||||
|
if nv(r_tasklist.rid_tasklist) = 0
|
||||||
|
then
|
||||||
|
select *
|
||||||
|
from core.tasklist tl
|
||||||
|
where
|
||||||
|
tl.rid_tasklist = r_taskitem.rid_tasklist
|
||||||
|
or nv(r_taskitem.rid_tasklist) = 0
|
||||||
|
and
|
||||||
|
(tl.rid_hub = m_rid_hub_payload
|
||||||
|
or tl.rid_hub = r_taskitem_parent.rid_hub
|
||||||
|
)
|
||||||
|
and tl.rid_mastertask in (
|
||||||
|
select mti.rid_mastertask
|
||||||
|
from core.mastertaskitem mti
|
||||||
|
where
|
||||||
|
mti.rid_mastertaskitem = r_mastertaskitemevent.rid_mastertaskitem
|
||||||
|
and coalesce(mti.inactive, 0) = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
order by tl.rid_tasklist desc
|
||||||
|
limit 1
|
||||||
|
into r_tasklist;
|
||||||
|
|
||||||
|
end if;
|
||||||
|
|
||||||
|
select tie.*
|
||||||
|
from core.taskitemevent tie
|
||||||
|
where
|
||||||
|
tie.rid_taskitem = r_taskitem.rid_taskitem
|
||||||
|
and tie.rid_mastertaskitemevent = r_mastertaskitemevent.rid_mastertaskitemevent
|
||||||
|
--and tie.status = r_mastertaskitemevent.status
|
||||||
|
order by tie.rid_taskitemevent desc
|
||||||
|
limit 1
|
||||||
|
into r_taskitemevent;
|
||||||
|
|
||||||
|
perform log_event(m_funcname, format(E'__Processing task item %s(%s, status=%s) for event %s(%s) status=%s'
|
||||||
|
, r_taskitem.description, r_taskitem.rid_taskitem, r_taskitem.status
|
||||||
|
, r_taskitemevent.description, r_mastertaskitemevent.rid_mastertaskitemevent, r_taskitemevent.status
|
||||||
|
), bt_enum('eventlog', 'local notice'));
|
||||||
|
|
||||||
|
if nv(r_taskitem.status) in (4, 5, 6, 11) --11 Resolved
|
||||||
|
and r_mastertaskitemevent.status is distinct from r_taskitem.status
|
||||||
|
and existS (
|
||||||
|
select 1
|
||||||
|
from core.taskitemevent e
|
||||||
|
where
|
||||||
|
e.rid_taskitem = r_taskitem.rid_taskitem
|
||||||
|
and e.status = r_taskitem.status
|
||||||
|
)
|
||||||
|
--Or errors to errors
|
||||||
|
-- or nv(r_taskitem.status) in (10, 11)
|
||||||
|
-- and nv(r_taskitemevent.status) in (1, 2, 11, 10)
|
||||||
|
-- and not exists (
|
||||||
|
-- select 1
|
||||||
|
-- from core.taskitem ti2
|
||||||
|
-- where ti2.rid_parenttaskitem = r_taskitem.rid_taskitem
|
||||||
|
-- and ti2.status in (1,2,10,11)
|
||||||
|
-- )
|
||||||
|
-- )
|
||||||
|
-- and nv(r_mastertaskitem.jsonvalue->'AOP'->>'errorcode') = ''
|
||||||
|
or p_parms ->> 'new_task' in ('1', 'true')
|
||||||
|
then
|
||||||
|
select r.p_retval, r.p_errmsg, r.p_a_rid_taskitem, r.p_parameters
|
||||||
|
from core.taskitem_get_or_prime(_jsonb_object_cat(
|
||||||
|
_jsonb_object_cat(r_taskitem.jsonvalue, jsonb_build_object('AOP', null, 'controls', null)),
|
||||||
|
p_parms
|
||||||
|
, jsonb_build_object(
|
||||||
|
'rid_mastertaskitem', r_mastertaskitemevent.rid_mastertaskitem
|
||||||
|
, 'rid_mastertaskitemevent', r_mastertaskitemevent.rid_mastertaskitemevent
|
||||||
|
, 'parent_rid_taskitemevent', r_taskitemevent.rid_taskitemevent
|
||||||
|
, 'rid_tasklist', r_tasklist.rid_tasklist
|
||||||
|
, 'parent_rid_taskitem'
|
||||||
|
, _bv(r_taskitem.rid_taskitem
|
||||||
|
, _try_integer(p_parms ->> 'parent_rid_taskitem', 0)
|
||||||
|
, r_taskitem_parent.rid_taskitem)
|
||||||
|
, 'new_item', false
|
||||||
|
, 'status', 1
|
||||||
|
, 'rid_tasklist_prev', _try_integers(p_parms ->> 'rid_tasklist_prev', p_parms ->> 'rid_tasklist',
|
||||||
|
r_taskitem.rid_tasklist::text)
|
||||||
|
, 'rid_taskitem_parentaction', p_parms -> 'rid_taskitem_parentaction'
|
||||||
|
)
|
||||||
|
)) r
|
||||||
|
into m_retval,m_errmsg,m_temp_rid_list,j_results;
|
||||||
|
|
||||||
|
if m_retval > 0
|
||||||
|
then
|
||||||
|
raise exception '%',m_errmsg using hint = 'in taskitem_get_or_prime 2';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
--raise notice 'Re-evaluated task item % for event % m_temp_rid_list=%',r_taskitem.rid_taskitem,r_mastertaskitemevent.rid_mastertaskitemevent, m_temp_rid_list;
|
||||||
|
|
||||||
|
if array_length(m_temp_rid_list, 1) > 1
|
||||||
|
and r_taskitem.rid_taskitem <> any (m_temp_rid_list)
|
||||||
|
or (p_parms ->> 'new_task' in ('1', 'true')
|
||||||
|
and r_taskitem.status in (4, 11)
|
||||||
|
)
|
||||||
|
|
||||||
|
then
|
||||||
|
perform log_event(m_funcname, format(E'*.* Fired event %s(%s) for completed action : %s New Actions: %s'
|
||||||
|
, r_mastertaskitemevent.description, r_mastertaskitemevent.rid_mastertaskitemevent, r_taskitem.description
|
||||||
|
, m_temp_rid_list), bt_enum('eventlog', 'debug'));
|
||||||
|
|
||||||
|
r_taskitem_prev = r_taskitem;
|
||||||
|
|
||||||
|
select ti.rid_taskitem
|
||||||
|
from core.taskitem ti
|
||||||
|
where
|
||||||
|
ti.rid_taskitem = any (m_temp_rid_list)
|
||||||
|
and ti.rid_mastertaskitem = r_taskitem.rid_mastertaskitem
|
||||||
|
into r_taskitem;
|
||||||
|
|
||||||
|
select *
|
||||||
|
from core.mastertaskitem mti
|
||||||
|
where mti.rid_mastertaskitem = r_taskitem.rid_mastertaskitem
|
||||||
|
into r_mastertaskitem;
|
||||||
|
|
||||||
|
r_taskitemevent = null;
|
||||||
|
|
||||||
|
-- else
|
||||||
|
--
|
||||||
|
-- perform log_event(m_funcname,format(E'Cannot fire event (Already Done,Cancelled,Error) (%s,%s,%s), on %s(%s) status = %s'
|
||||||
|
-- ,r_mastertaskitemevent.description
|
||||||
|
-- ,r_mastertaskitemevent.rid_mastertaskitemevent
|
||||||
|
-- ,r_mastertaskitemevent.status
|
||||||
|
-- ,r_taskitem.rid_taskitem
|
||||||
|
-- ,r_taskitem.description
|
||||||
|
-- ,r_taskitem.status
|
||||||
|
-- ),bt_enum('eventlog','debug'));
|
||||||
|
--
|
||||||
|
-- continue;
|
||||||
|
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
---Allow firing events again.
|
||||||
|
if nv(r_taskitemevent.rid_taskitemevent) > 0
|
||||||
|
then
|
||||||
|
-- if nv(r_taskitemevent.retval) < 2
|
||||||
|
-- then
|
||||||
|
|
||||||
|
perform log_event(m_funcname, format(E'**Updating event %s(%s) for action : %s'
|
||||||
|
, r_mastertaskitemevent.description, r_mastertaskitemevent.rid_mastertaskitemevent, r_taskitem.description
|
||||||
|
), bt_enum('eventlog', 'debug'));
|
||||||
|
|
||||||
|
--SET session_replication_role = DEFAULT;
|
||||||
|
|
||||||
|
update core.taskitemevent u
|
||||||
|
set
|
||||||
|
retval = 0
|
||||||
|
, createddatetime = now()
|
||||||
|
, jsonvalue = _jsonb_object_cat(u.jsonvalue, p_parms -> '_event_jsonvalue',
|
||||||
|
jsonb_build_object('rid_mastertaskitem_complete', case
|
||||||
|
when
|
||||||
|
_try_integer(
|
||||||
|
r_taskitemevent_parent.jsonvalue ->>
|
||||||
|
'rid_mastertaskitem_complete',
|
||||||
|
0) <>
|
||||||
|
r_mastertaskitemevent.rid_mastertaskitem
|
||||||
|
then r_taskitemevent_parent.jsonvalue ->> 'rid_mastertaskitem_complete'
|
||||||
|
else null
|
||||||
|
end
|
||||||
|
))
|
||||||
|
where u.rid_taskitemevent = r_taskitemevent.rid_taskitemevent
|
||||||
|
returning u.*
|
||||||
|
into r_taskitemevent;
|
||||||
|
|
||||||
|
|
||||||
|
select r.p_retval, r.p_errmsg
|
||||||
|
from core.event_created(r_taskitemevent.rid_taskitemevent) r
|
||||||
|
into m_retval,m_errmsg;
|
||||||
|
|
||||||
|
if m_retval > 0
|
||||||
|
then
|
||||||
|
raise exception '%',m_errmsg;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
|
||||||
|
update core.taskitemevent u
|
||||||
|
set
|
||||||
|
jsonvalue = _jsonb_object_cat(u.jsonvalue, jsonb_build_object('rid_taskitemevent_retries',
|
||||||
|
_jsonb_object_cat(u.jsonvalue ->
|
||||||
|
'rid_taskitemevent_retries',
|
||||||
|
jsonb_build_array(r_taskitemevent.rid_taskitemevent))))
|
||||||
|
where u.rid_taskitemevent = r_taskitemevent_parent.rid_taskitemevent;
|
||||||
|
|
||||||
|
-- end if;
|
||||||
|
else
|
||||||
|
--SET session_replication_role = DEFAULT;
|
||||||
|
|
||||||
|
insert
|
||||||
|
into core.taskitemevent( createddatetime
|
||||||
|
, description
|
||||||
|
, guid
|
||||||
|
, rid_mastertaskitemevent
|
||||||
|
, rid_taskitem
|
||||||
|
, duedatetime
|
||||||
|
, errmsg
|
||||||
|
, escalated
|
||||||
|
, outcome
|
||||||
|
, status
|
||||||
|
, jsonvalue)
|
||||||
|
select now()
|
||||||
|
, format('%s %s', evt.description, nv((
|
||||||
|
select count(1)
|
||||||
|
from core.taskitemevent ev2
|
||||||
|
where
|
||||||
|
ev2.rid_taskitem = r_taskitem.rid_taskitem
|
||||||
|
and ev2.rid_mastertaskitemevent = r_mastertaskitemevent.rid_mastertaskitemevent
|
||||||
|
)) + 1
|
||||||
|
)
|
||||||
|
, newid()
|
||||||
|
, r_mastertaskitemevent.rid_mastertaskitemevent
|
||||||
|
, r_taskitem.rid_taskitem
|
||||||
|
, (
|
||||||
|
select eri.p_timestamp
|
||||||
|
from core.interval_fetch('mastertaskitemevent', r_mastertaskitemevent.rid_mastertaskitemevent
|
||||||
|
, r_taskitem.rid_hub, 'duedatetime',
|
||||||
|
jsonb_build_object('rid_taskitem', r_taskitem.rid_taskitem)) eri
|
||||||
|
limit 1
|
||||||
|
)
|
||||||
|
, null
|
||||||
|
, _try_integer(p_parms ->> 'escalated', 0)
|
||||||
|
, evt.outcome
|
||||||
|
, evt.status
|
||||||
|
, _jsonb_object_cat(p_parms -> '_event_jsonvalue', case
|
||||||
|
when nv(r_taskitem.rid_taskitem) = 0
|
||||||
|
then jsonb_build_object('rid_hub', m_rid_hub_payload)
|
||||||
|
else jsonb_build_object()
|
||||||
|
end
|
||||||
|
, jsonb_build_object('parent_rid_taskitem',
|
||||||
|
_try_integer(p_parms ->> 'parent_rid_taskitem', r_taskitem.rid_taskitem)
|
||||||
|
, 'parent_rid_taskitemevent', r_taskitemevent_parent.rid_taskitemevent
|
||||||
|
, 'rid_mastertaskitem_complete', case
|
||||||
|
when _try_integer(
|
||||||
|
r_taskitemevent_parent.jsonvalue ->>
|
||||||
|
'rid_mastertaskitem_complete', 0) <>
|
||||||
|
evt.rid_mastertaskitem
|
||||||
|
then r_taskitemevent_parent.jsonvalue ->> 'rid_mastertaskitem_complete'
|
||||||
|
else null
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
from core.mastertaskitemevent evt
|
||||||
|
where evt.rid_mastertaskitemevent = r_mastertaskitemevent.rid_mastertaskitemevent
|
||||||
|
returning taskitemevent.*
|
||||||
|
into r_taskitemevent;
|
||||||
|
|
||||||
|
if r_taskitemevent.rid_taskitemevent > 0
|
||||||
|
then
|
||||||
|
perform log_event(m_funcname, format(E'**Created event %s(%s) for action : %s'
|
||||||
|
, r_mastertaskitemevent.description, r_mastertaskitemevent.rid_mastertaskitemevent, r_taskitem.description
|
||||||
|
), bt_enum('eventlog', 'debug'));
|
||||||
|
|
||||||
|
|
||||||
|
update core.taskitemevent u
|
||||||
|
set
|
||||||
|
jsonvalue = _jsonb_object_cat(u.jsonvalue, jsonb_build_object('rid_taskitemevent_spawned',
|
||||||
|
_jsonb_object_cat(u.jsonvalue ->
|
||||||
|
'rid_taskitemevent_spawned',
|
||||||
|
jsonb_build_array(r_taskitemevent.rid_taskitemevent))))
|
||||||
|
where u.rid_taskitemevent = r_taskitemevent_parent.rid_taskitemevent;
|
||||||
|
|
||||||
|
|
||||||
|
end if;
|
||||||
|
|
||||||
|
end if;
|
||||||
|
|
||||||
|
/*
|
||||||
|
if nv(r_taskitemevent.rid_taskitemevent) > 0
|
||||||
|
then
|
||||||
|
-- if nv(r_taskitemevent.retval) < 2
|
||||||
|
-- then
|
||||||
|
|
||||||
|
perform log_event(m_funcname,format(E'Updating event %s(%s) for action : %s'
|
||||||
|
,r_mastertaskitemevent.description,r_mastertaskitemevent.rid_mastertaskitemevent, r_taskitem.description
|
||||||
|
),bt_enum('eventlog','debug'));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
perform meta.add_transaction_event(txid_current(), 'core.action_init'
|
||||||
|
,replace(replace(replace(replace(replace($CC$
|
||||||
|
DO $ICC$
|
||||||
|
DECLARE
|
||||||
|
m_retval integer;
|
||||||
|
m_errmsg text;
|
||||||
|
r_taskitemevent core.taskitemevent%rowtype;
|
||||||
|
r_taskitemevent_parent core.taskitemevent%rowtype;
|
||||||
|
r_mastertaskitemevent core.mastertaskitemevent%rowtype;
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
select *
|
||||||
|
from core.taskitemevent tie
|
||||||
|
where tie.rid_taskitemevent = [rid_taskitemevent]
|
||||||
|
into r_taskitemevent;
|
||||||
|
|
||||||
|
select *
|
||||||
|
from core.taskitemevent tie
|
||||||
|
where tie.rid_taskitemevent = [rid_taskitemevent_parent]
|
||||||
|
into r_taskitemevent_parent;
|
||||||
|
|
||||||
|
select *
|
||||||
|
from core.mastertaskitemevent t
|
||||||
|
where t.rid_mastertaskitemevent = [rid_mastertaskitemevent]
|
||||||
|
into r_mastertaskitemevent;
|
||||||
|
|
||||||
|
|
||||||
|
update core.taskitemevent u
|
||||||
|
set
|
||||||
|
retval = 0
|
||||||
|
, createddatetime = now()
|
||||||
|
, jsonvalue = _jsonb_object_cat(u.jsonvalue,$JJA$[event_json_value]$JJA$::jsonb, jsonb_build_object('rid_mastertaskitem_complete', case
|
||||||
|
when
|
||||||
|
_try_integer(
|
||||||
|
r_taskitemevent_parent.jsonvalue ->>
|
||||||
|
'rid_mastertaskitem_complete',
|
||||||
|
0) <>
|
||||||
|
r_mastertaskitemevent.rid_mastertaskitem
|
||||||
|
then r_taskitemevent_parent.jsonvalue ->> 'rid_mastertaskitem_complete'
|
||||||
|
else null
|
||||||
|
end
|
||||||
|
))
|
||||||
|
where u.rid_taskitemevent = r_taskitemevent.rid_taskitemevent
|
||||||
|
returning u.*
|
||||||
|
into r_taskitemevent;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
select r.p_retval,r.p_errmsg
|
||||||
|
from core.event_created(r_taskitemevent.rid_taskitemevent) r
|
||||||
|
into m_retval,m_errmsg;
|
||||||
|
|
||||||
|
if m_retval > 0
|
||||||
|
then
|
||||||
|
raise exception '%',m_errmsg;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
|
||||||
|
update core.taskitemevent u
|
||||||
|
set
|
||||||
|
jsonvalue = _jsonb_object_cat(u.jsonvalue, jsonb_build_object('rid_taskitemevent_retries',
|
||||||
|
_jsonb_object_cat(u.jsonvalue -> 'rid_taskitemevent_retries',
|
||||||
|
jsonb_build_array(r_taskitemevent.rid_taskitemevent))))
|
||||||
|
where u.rid_taskitemevent = r_taskitemevent_parent.rid_taskitemevent;
|
||||||
|
|
||||||
|
|
||||||
|
END;
|
||||||
|
$ICC$;
|
||||||
|
$CC$,'[rid_taskitemevent_parent]', nv(r_taskitemevent_parent.rid_taskitemevent)::text)
|
||||||
|
,'[rid_taskitemevent]', nv(r_taskitemevent.rid_taskitemevent)::text)
|
||||||
|
,'[rid_mastertaskitemevent]', nv(r_mastertaskitemevent.rid_mastertaskitemevent)::text)
|
||||||
|
,'[rid_taskitem]', nv(r_taskitem.rid_taskitem)::text)
|
||||||
|
,'[event_json_value]' ,coalesce((p_parms->'_event_jsonvalue')::text,'{}'))
|
||||||
|
)
|
||||||
|
,30, format('action_init_%s',r_taskitemevent.rid_taskitemevent);
|
||||||
|
|
||||||
|
SET session_replication_role = DEFAULT;
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
-------------------------------------------__Recursive Rules__---------------------------------------------------
|
||||||
|
-- --activate rule
|
||||||
|
-- if r_taskitemevent_parent.rid_taskitemevent > 0
|
||||||
|
-- and r_taskitemevent_parent.jsonvalue->>'rid_mastertaskitem_activate' is not null
|
||||||
|
-- and _try_integer(r_taskitemevent_parent.jsonvalue->>'rid_mastertaskitem_activate',-1) > 0
|
||||||
|
-- and not exists (
|
||||||
|
-- select 1
|
||||||
|
-- from core.taskitem ti
|
||||||
|
-- where ti.rid_mastertaskitem = _try_integer(r_taskitemevent_parent.jsonvalue->>'rid_mastertaskitem_activate',-1)
|
||||||
|
-- and ti.rid_hub = m_rid_hub_payload
|
||||||
|
-- )
|
||||||
|
-- then
|
||||||
|
-- if _try_integer(r_taskitemevent_parent.jsonvalue->>'rid_mastertaskitem_activate',-1) <> r_taskitem.rid_mastertaskitem
|
||||||
|
-- and _try_integer(r_taskitemevent_parent.jsonvalue->>'rid_mastertaskitem_activate',-1) <> nv(r_mastertaskitem.rid_parentmastertaskitem)
|
||||||
|
-- then
|
||||||
|
-- insert into core.taskitemevent(rid_taskitem,rid_mastertaskitemevent,description,status,createddatetime,guid,jsonvalue)
|
||||||
|
-- select r_taskitem.rid_taskitem
|
||||||
|
-- , mtev.rid_mastertaskitemevent
|
||||||
|
-- , mtev.description
|
||||||
|
-- , mtev.status
|
||||||
|
-- , now()
|
||||||
|
-- , newid()
|
||||||
|
-- , _jsonb_object_cat(mtev.jsonvalue,p_parms->'_event_jsonvalue', jsonb_build_object('rid_mastertaskitem_activate',r_taskitemevent_parent.jsonvalue->>'rid_mastertaskitem_activate', 'simulated', true
|
||||||
|
-- , 'parent_rid_taskitem',_try_integer(p_parms ->> 'parent_rid_taskitem',0)
|
||||||
|
-- ))
|
||||||
|
-- from core.mastertaskitemevent mtev
|
||||||
|
-- where mtev.rid_mastertaskitem = r_taskitem.rid_mastertaskitem
|
||||||
|
-- and coalesce(mtev.inactive,0) = 0
|
||||||
|
-- and mtev.status = 2
|
||||||
|
-- and not exists (
|
||||||
|
-- select 1
|
||||||
|
-- from core.taskitemevent tie
|
||||||
|
-- where tie.rid_taskitem = r_taskitem.rid_taskitem
|
||||||
|
-- and tie.rid_mastertaskitemevent = mtev.rid_mastertaskitemevent
|
||||||
|
-- )
|
||||||
|
-- ;
|
||||||
|
-- elseif _try_integer(r_taskitemevent_parent.jsonvalue->>'rid_mastertaskitem_activate',-1) = r_taskitem.rid_mastertaskitem
|
||||||
|
-- then
|
||||||
|
-- --We reached the target taskitem, so we can mark it activate
|
||||||
|
-- insert into core.taskitemevent(rid_taskitem,rid_mastertaskitemevent,description,status,createddatetime,guid,jsonvalue)
|
||||||
|
-- select r_taskitem.rid_taskitem
|
||||||
|
-- , mtev.rid_mastertaskitemevent
|
||||||
|
-- , mtev.description
|
||||||
|
-- , mtev.status
|
||||||
|
-- , now()
|
||||||
|
-- , newid()
|
||||||
|
-- , _jsonb_object_cat( mtev.jsonvalue,p_parms->'_event_jsonvalue',jsonb_build_object('parent_rid_taskitem',_try_integer(p_parms ->> 'parent_rid_taskitem',0)))
|
||||||
|
-- from core.mastertaskitemevent mtev
|
||||||
|
-- where mtev.rid_mastertaskitem = r_taskitem.rid_mastertaskitem
|
||||||
|
-- and coalesce(mtev.inactive,0) = 0
|
||||||
|
-- and mtev.status = 2
|
||||||
|
-- and not exists (
|
||||||
|
-- select 1
|
||||||
|
-- from core.taskitemevent tie
|
||||||
|
-- where tie.rid_taskitem = r_taskitem.rid_taskitem
|
||||||
|
-- and tie.rid_mastertaskitemevent = mtev.rid_mastertaskitemevent
|
||||||
|
-- )
|
||||||
|
-- ;
|
||||||
|
-- end if;
|
||||||
|
--
|
||||||
|
-- end if;
|
||||||
|
--
|
||||||
|
-- --complete rule
|
||||||
|
-- if r_taskitemevent_parent.rid_taskitemevent > 0
|
||||||
|
-- and r_taskitemevent_parent.jsonvalue->>'rid_mastertaskitem_complete' is not null
|
||||||
|
-- and _try_integer(r_taskitemevent_parent.jsonvalue->>'rid_mastertaskitem_complete',-1) > 0
|
||||||
|
-- and not exists (
|
||||||
|
-- select 1
|
||||||
|
-- from core.taskitem ti
|
||||||
|
-- where ti.rid_mastertaskitem = _try_integer(r_taskitemevent_parent.jsonvalue->>'rid_mastertaskitem_complete',-1)
|
||||||
|
-- and ti.rid_hub = m_rid_hub_payload
|
||||||
|
-- )
|
||||||
|
-- then
|
||||||
|
-- if _try_integer(r_taskitemevent_parent.jsonvalue->>'rid_mastertaskitem_complete',-1) <> r_taskitem.rid_mastertaskitem
|
||||||
|
-- and _try_integer(r_taskitemevent_parent.jsonvalue->>'rid_mastertaskitem_complete',-1) <> nv(r_mastertaskitem.rid_parentmastertaskitem)
|
||||||
|
-- then
|
||||||
|
-- insert into core.taskitemevent(rid_taskitem,rid_mastertaskitemevent,description,status,createddatetime,guid,jsonvalue)
|
||||||
|
-- select r_taskitem.rid_taskitem
|
||||||
|
-- , mtev.rid_mastertaskitemevent
|
||||||
|
-- , mtev.description
|
||||||
|
-- , mtev.status
|
||||||
|
-- , now()
|
||||||
|
-- , newid()
|
||||||
|
-- , _jsonb_object_cat(mtev.jsonvalue,p_parms->'_event_jsonvalue', jsonb_build_object('rid_mastertaskitem_complete',r_taskitemevent_parent.jsonvalue->>'rid_mastertaskitem_complete', 'simulated', true, 'parent_rid_taskitem',_try_integer(p_parms ->> 'parent_rid_taskitem',0)))
|
||||||
|
-- from core.mastertaskitemevent mtev
|
||||||
|
-- where mtev.rid_mastertaskitem = r_taskitem.rid_mastertaskitem
|
||||||
|
-- and coalesce(mtev.inactive,0) = 0
|
||||||
|
-- and mtev.status = 4
|
||||||
|
-- and not exists (
|
||||||
|
-- select 1
|
||||||
|
-- from core.taskitemevent tie
|
||||||
|
-- where tie.rid_taskitem = r_taskitem.rid_taskitem
|
||||||
|
-- and tie.rid_mastertaskitemevent = mtev.rid_mastertaskitemevent
|
||||||
|
-- )
|
||||||
|
-- ;
|
||||||
|
-- elseif _try_integer(r_taskitemevent_parent.jsonvalue->>'rid_mastertaskitem_complete',-1) = r_taskitem.rid_mastertaskitem
|
||||||
|
-- then
|
||||||
|
-- --We reached the target taskitem, so we can mark it activate
|
||||||
|
-- insert into core.taskitemevent(rid_taskitem,rid_mastertaskitemevent,description,status,createddatetime,guid,jsonvalue)
|
||||||
|
-- select r_taskitem.rid_taskitem
|
||||||
|
-- , mtev.rid_mastertaskitemevent
|
||||||
|
-- , mtev.description
|
||||||
|
-- , mtev.status
|
||||||
|
-- , now()
|
||||||
|
-- , newid()
|
||||||
|
-- , _jsonb_object_cat(mtev.jsonvalue,p_parms->'_event_jsonvalue',jsonb_build_object('parent_rid_taskitem',_try_integer(p_parms ->> 'parent_rid_taskitem',0)))
|
||||||
|
-- from core.mastertaskitemevent mtev
|
||||||
|
-- where mtev.rid_mastertaskitem = r_taskitem.rid_mastertaskitem
|
||||||
|
-- and coalesce(mtev.inactive,0) = 0
|
||||||
|
-- and mtev.status = 2
|
||||||
|
-- and not exists (
|
||||||
|
-- select 1
|
||||||
|
-- from core.taskitemevent tie
|
||||||
|
-- where tie.rid_taskitem = r_taskitem.rid_taskitem
|
||||||
|
-- and tie.rid_mastertaskitemevent = mtev.rid_mastertaskitemevent
|
||||||
|
-- )
|
||||||
|
-- ;
|
||||||
|
-- end if;
|
||||||
|
-- end if;
|
||||||
|
|
||||||
|
p_parms := _jsonb_object_cat(p_parms, to_jsonb(r_taskitemevent));
|
||||||
|
|
||||||
|
end loop;
|
||||||
|
|
||||||
|
-- perform log_event(m_funcname
|
||||||
|
-- ,format('Post event insert: rid_taskitemevent=%s rid_mastertaskitem_complete=%s rid_mastertaskitem=%s'
|
||||||
|
-- ,r_taskitemevent_parent.rid_taskitemevent
|
||||||
|
-- ,r_taskitemevent_parent.jsonvalue->>'rid_mastertaskitem_complete'
|
||||||
|
-- ,r_taskitem.rid_mastertaskitem
|
||||||
|
-- ),bt_enum('eventlog','local notice'));
|
||||||
|
|
||||||
|
elsif p_table::citext in ('taskitem', 'wfl')
|
||||||
|
then
|
||||||
|
select ti.*
|
||||||
|
from core.taskitem ti
|
||||||
|
where ti.rid_taskitem = p_rid
|
||||||
|
into r_taskitem;
|
||||||
|
|
||||||
|
select ti.*
|
||||||
|
from core.mastertaskitem ti
|
||||||
|
where ti.rid_mastertaskitem = r_taskitem.rid_mastertaskitem
|
||||||
|
into r_mastertaskitem;
|
||||||
|
|
||||||
|
select tl.*
|
||||||
|
from core.tasklist tl
|
||||||
|
where tl.rid_tasklist = r_taskitem.rid_tasklist
|
||||||
|
into r_tasklist;
|
||||||
|
|
||||||
|
--Create item linked to task for this hub if not found
|
||||||
|
|
||||||
|
perform log_event(m_funcname, format(E'Action Init via %s=%s Item:%s(%s rid_taskitem:%s)
|
||||||
|
p_parms:%s', p_table, p_rid
|
||||||
|
, r_mastertaskitem.description, r_mastertaskitem.rid_mastertaskitem, r_taskitem.rid_taskitem
|
||||||
|
, p_parms), bt_enum('eventlog', 'local notice'));
|
||||||
|
|
||||||
|
select r.p_retval, r.p_errmsg, r.p_a_rid_taskitem
|
||||||
|
from core.taskitem_get_or_prime(_jsonb_object_cat(
|
||||||
|
jsonb_build_object('rid_tasklist', r_tasklist.rid_tasklist
|
||||||
|
, 'rid_mastertaskitemevent',
|
||||||
|
r_mastertaskitemevent.rid_mastertaskitemevent
|
||||||
|
, 'rid_taskitem_parentaction', p_parms -> 'rid_taskitem_parentaction'
|
||||||
|
), to_jsonb(r_taskitem), p_parms)) r
|
||||||
|
into m_retval,m_errmsg,m_temp_rid_list;
|
||||||
|
|
||||||
|
if m_retval > 0
|
||||||
|
then
|
||||||
|
raise exception '%',m_errmsg using hint = 'in taskitem_get_or_prime 3';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if array_length(m_temp_rid_list, 1) > 1
|
||||||
|
then
|
||||||
|
select array_agg(to_jsonb(ti))
|
||||||
|
from core.taskitem ti
|
||||||
|
where ti.rid_taskitem = any (m_temp_rid_list)
|
||||||
|
into p_parms;
|
||||||
|
|
||||||
|
else
|
||||||
|
m_temp_rid = m_temp_rid_list[1];
|
||||||
|
select *
|
||||||
|
from core.taskitem ti
|
||||||
|
where ti.rid_taskitem = m_temp_rid
|
||||||
|
into r_taskitem;
|
||||||
|
|
||||||
|
p_parms = to_jsonb(r_taskitem);
|
||||||
|
end if;
|
||||||
|
|
||||||
|
|
||||||
|
elsif p_table::citext in ('mastertaskitem', 'mal')
|
||||||
|
then
|
||||||
|
select ti.*
|
||||||
|
from core.mastertaskitem ti
|
||||||
|
where ti.rid_mastertaskitem = p_rid
|
||||||
|
into r_mastertaskitem;
|
||||||
|
|
||||||
|
if r_mastertaskitem.inactive > 0
|
||||||
|
then
|
||||||
|
raise exception 'The template task item is inactive. rid_mastertaskitem = %',r_mastertaskitem.rid_mastertaskitem;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
perform log_event(m_funcname, format(E'Action Init via %s=%s Item:%s(%s)
|
||||||
|
p_parms:%s', p_table, p_rid
|
||||||
|
, r_mastertaskitem.description, r_mastertaskitem.rid_mastertaskitem
|
||||||
|
, p_parms), bt_enum('eventlog', 'local notice'));
|
||||||
|
|
||||||
|
select r.p_retval, r.p_errmsg, r.p_a_rid_taskitem
|
||||||
|
from core.taskitem_get_or_prime(_jsonb_object_cat(to_jsonb(r_mastertaskitem)
|
||||||
|
, p_parms
|
||||||
|
, jsonb_build_object('rid_mastertaskitemevent', r_mastertaskitemevent.rid_mastertaskitemevent
|
||||||
|
, 'rid_taskitem_parentaction',
|
||||||
|
p_parms -> 'rid_taskitem_parentaction'
|
||||||
|
))
|
||||||
|
) r
|
||||||
|
into m_retval,m_errmsg,m_temp_rid_list;
|
||||||
|
|
||||||
|
if m_retval > 0
|
||||||
|
then
|
||||||
|
raise exception '%',m_errmsg using hint = 'in taskitem_get_or_prime 4';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if array_length(m_temp_rid_list, 1) > 1
|
||||||
|
then
|
||||||
|
select array_agg(to_jsonb(ti))
|
||||||
|
from core.taskitem ti
|
||||||
|
where ti.rid_taskitem = any (m_temp_rid_list)
|
||||||
|
into p_parms;
|
||||||
|
|
||||||
|
else
|
||||||
|
m_temp_rid = m_temp_rid_list[1];
|
||||||
|
select *
|
||||||
|
from core.taskitem ti
|
||||||
|
where ti.rid_taskitem = m_temp_rid
|
||||||
|
into r_taskitem;
|
||||||
|
|
||||||
|
p_parms = to_jsonb(r_taskitem);
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if nv(r_taskitem.rid_mastertaskitem) = 0
|
||||||
|
then
|
||||||
|
raise warning 'No template for the task item. rid_mastertaskitem = %',r_taskitem.rid_mastertaskitem;
|
||||||
|
|
||||||
|
end if;
|
||||||
|
|
||||||
|
|
||||||
|
EXCEPTION
|
||||||
|
WHEN others THEN
|
||||||
|
GET STACKED DIAGNOSTICS
|
||||||
|
m_errmsg = MESSAGE_TEXT
|
||||||
|
,m_errcontext = PG_EXCEPTION_CONTEXT
|
||||||
|
,m_errdetail = PG_EXCEPTION_DETAIL
|
||||||
|
,m_errhint = PG_EXCEPTION_HINT
|
||||||
|
,m_errstate = RETURNED_SQLSTATE;
|
||||||
|
|
||||||
|
p_errmsg := format('%s table:%s rid:%s',
|
||||||
|
get_err_msg(m_funcname, m_errmsg, m_errcontext, m_errdetail, m_errhint, m_errstate), p_table,
|
||||||
|
p_rid);
|
||||||
|
p_retval = 1;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
+988
@@ -0,0 +1,988 @@
|
|||||||
|
--select * from dropall('event_exec_func','core');
|
||||||
|
CREATE OR REPLACE FUNCTION core.event_exec_func(
|
||||||
|
p_event_parameters jsonb
|
||||||
|
, p_item_parameters jsonb
|
||||||
|
, OUT p_retval integer
|
||||||
|
, OUT p_errorcode text
|
||||||
|
, OUT p_errmsg text
|
||||||
|
, OUT p_output jsonb
|
||||||
|
)
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
VOLATILE
|
||||||
|
SECURITY DEFINER
|
||||||
|
AS
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
--Error Handling--
|
||||||
|
m_funcname text = 'core.event_exec_func';
|
||||||
|
m_errmsg text;
|
||||||
|
m_errcode text;
|
||||||
|
m_errcontext text;
|
||||||
|
m_errdetail text;
|
||||||
|
m_errhint text;
|
||||||
|
m_errstate text;
|
||||||
|
m_retval integer;
|
||||||
|
--Error Handling--
|
||||||
|
r_taskitem core.taskitem%rowtype;
|
||||||
|
r_taskitem_parent core.taskitem%rowtype;
|
||||||
|
r_mastertaskitem core.mastertaskitem%rowtype;
|
||||||
|
r_taskitemevent core.taskitemevent%rowtype;
|
||||||
|
m_rid integer;
|
||||||
|
j_output jsonb;
|
||||||
|
j_all_parms jsonb;
|
||||||
|
j_controls jsonb;
|
||||||
|
j_tmp jsonb;
|
||||||
|
m_exec_funcname citext;
|
||||||
|
m_exec_schema citext;
|
||||||
|
m_sql citext;
|
||||||
|
r_lp record;
|
||||||
|
r_hub core.hub%rowtype;
|
||||||
|
a_async_procs citext[];
|
||||||
|
a_broker_procs citext[];
|
||||||
|
a_noerror_procs citext[];
|
||||||
|
BEGIN
|
||||||
|
j_controls = jsonb_build_array();
|
||||||
|
p_retval = 0;
|
||||||
|
p_errmsg = '';
|
||||||
|
m_exec_schema = 'public';
|
||||||
|
a_async_procs = array ['']; --hsync_upload_clienttohyphen
|
||||||
|
a_broker_procs = array ['']; --hsync_upload_clienttohyphen
|
||||||
|
a_noerror_procs = array ['ui_clientscenario_commit'];
|
||||||
|
|
||||||
|
-- if p_event_parameters -> 'AFN_RID_ACTIONFUNCTION_CODE' is null
|
||||||
|
-- then
|
||||||
|
-- p_retval = 1;
|
||||||
|
-- p_errmsg = '[ECR0001] No AFN_RID_ACTIONFUNCTION_CODE inside the AOP pack.';
|
||||||
|
--
|
||||||
|
-- select m.v[1]
|
||||||
|
-- from regexp_matches(p_errmsg, '\[([^\]]+)\]','ig') m(v)
|
||||||
|
-- where length(m.v[1]) between 2 and 10
|
||||||
|
-- order by m.v[1]
|
||||||
|
-- into p_errorcode;
|
||||||
|
--
|
||||||
|
-- return;
|
||||||
|
-- end if;
|
||||||
|
|
||||||
|
select *
|
||||||
|
from core.taskitemevent tie
|
||||||
|
where tie.rid_taskitemevent = _try_integer(p_item_parameters ->> 'rid_taskitemevent', 0)
|
||||||
|
into r_taskitemevent;
|
||||||
|
|
||||||
|
select *
|
||||||
|
from core.taskitem ti
|
||||||
|
where ti.rid_taskitem = _try_integer(p_item_parameters ->> 'rid_taskitem', r_taskitemevent.rid_taskitem)
|
||||||
|
into r_taskitem;
|
||||||
|
|
||||||
|
select *
|
||||||
|
from core.mastertaskitem mti
|
||||||
|
where
|
||||||
|
mti.rid_mastertaskitem = _try_integer(p_item_parameters ->> 'rid_mastertaskitem', 0)
|
||||||
|
or r_taskitem.rid_mastertaskitem > 0 and mti.rid_mastertaskitem = r_taskitem.rid_mastertaskitem
|
||||||
|
order by mti.rid_mastertaskitem desc
|
||||||
|
limit 1
|
||||||
|
into r_mastertaskitem;
|
||||||
|
|
||||||
|
m_exec_funcname = r_mastertaskitem.functionname_code;
|
||||||
|
|
||||||
|
if m_exec_funcname ilike '%.%'
|
||||||
|
then
|
||||||
|
m_exec_schema = split_part(m_exec_funcname, '.', 1);
|
||||||
|
m_exec_funcname = split_part(m_exec_funcname, '.', 2);
|
||||||
|
end if;
|
||||||
|
|
||||||
|
|
||||||
|
if nv(m_exec_funcname) = ''
|
||||||
|
then
|
||||||
|
perform log_event(m_funcname, format(
|
||||||
|
E'No function to execute for MasterTaskItem %s (rid_mastertaskitem=%s) and TaskItem %s (rid_taskitem=%s)',
|
||||||
|
r_mastertaskitem.description, r_mastertaskitem.rid_mastertaskitem, r_taskitem.description,
|
||||||
|
r_taskitem.rid_taskitem), bt_enum('eventlog', 'local notice'));
|
||||||
|
return;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
-- if m_exec_funcname ilike '%payment%plan%'
|
||||||
|
-- then
|
||||||
|
-- raise exception 'Payment Plan Test Error % [RTY0001]', m_exec_funcname;
|
||||||
|
-- end if;
|
||||||
|
|
||||||
|
|
||||||
|
j_all_parms = _jsonb_object_cat(r_taskitem.jsonvalue,
|
||||||
|
j_all_parms
|
||||||
|
, (
|
||||||
|
select jsonb_object_agg(k, p_event_parameters -> k)
|
||||||
|
from jsonb_object_keys(p_event_parameters) keys(k)
|
||||||
|
where jsonb_typeof(p_event_parameters -> keys.k) not in ('object', 'array', 'null')
|
||||||
|
), (
|
||||||
|
select jsonb_object_agg(k, p_item_parameters -> k)
|
||||||
|
from jsonb_object_keys(p_item_parameters) keys(k)
|
||||||
|
where jsonb_typeof(p_item_parameters -> keys.k) not in ('object', 'array', 'null')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
if r_taskitem.rid_taskitem > 0 and r_taskitem.rid_taskitem_parentaction > 0
|
||||||
|
then
|
||||||
|
select ti.*
|
||||||
|
from core.taskitem ti
|
||||||
|
where ti.rid_taskitem = r_taskitem.rid_taskitem_parentaction
|
||||||
|
into r_taskitem_parent;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
select af.jsonvalue -> 'controls'
|
||||||
|
from core.actionfunction af
|
||||||
|
where
|
||||||
|
(
|
||||||
|
af.functionname = m_exec_funcname
|
||||||
|
or af.functionname = r_mastertaskitem.functionname_code
|
||||||
|
)
|
||||||
|
and af.fntype = 'pgsql-function'::citext
|
||||||
|
into j_controls;
|
||||||
|
|
||||||
|
|
||||||
|
if jsonb_typeof(p_item_parameters -> 'AOP' -> 'jsonvalue' -> 'controls_code') = 'array'
|
||||||
|
then
|
||||||
|
j_controls = _jsonb_object_cat(j_controls, p_item_parameters -> 'AOP' -> 'jsonvalue' -> 'controls');
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if jsonb_typeof(p_event_parameters -> 'jsonvalue' -> 'controls_code') = 'array'
|
||||||
|
then
|
||||||
|
j_controls = _jsonb_object_cat(j_controls, p_event_parameters -> 'jsonvalue' -> 'controls');
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if jsonb_typeof(j_all_parms -> 'controls_code') = 'array'
|
||||||
|
then
|
||||||
|
j_controls = _jsonb_object_cat(j_controls, j_all_parms -> 'controls_code');
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if jsonb_typeof(r_mastertaskitem.jsonvalue -> 'controls_code') = 'array'
|
||||||
|
then
|
||||||
|
j_controls = _jsonb_object_cat(j_controls, r_mastertaskitem.jsonvalue -> 'controls_code');
|
||||||
|
end if;
|
||||||
|
|
||||||
|
raise notice 'Controls: %', j_controls::text;
|
||||||
|
|
||||||
|
j_all_parms = _jsonb_object_cat(j_all_parms,
|
||||||
|
(
|
||||||
|
select jsonb_object_agg(ctr.j ->> 'name'
|
||||||
|
, case
|
||||||
|
when nv(j_all_parms ->> (ctr.j ->> 'name')) = ''
|
||||||
|
then ctr.j -> 'props' ->> 'value'
|
||||||
|
else j_all_parms ->> (ctr.j ->> 'name')
|
||||||
|
end
|
||||||
|
order by i
|
||||||
|
)
|
||||||
|
from jsonb_array_elements(j_controls) with ordinality ctr(j, i)
|
||||||
|
where nv(ctr.j -> 'props' ->> 'value') <> ''
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
if nv(j_all_parms ->> 'rid_hub_user') = ''
|
||||||
|
then
|
||||||
|
j_all_parms = _jsonb_object_cat(j_all_parms, jsonb_build_object(
|
||||||
|
'rid_hub_user', _bv(core.f_get_user_hub_rid(), (
|
||||||
|
select hub.rid_hub
|
||||||
|
from core.hub
|
||||||
|
where hub.hubtype = 'program'
|
||||||
|
limit 1
|
||||||
|
)),
|
||||||
|
'login', f_getuser()
|
||||||
|
));
|
||||||
|
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if nv(j_all_parms ->> 'login') = ''
|
||||||
|
then
|
||||||
|
j_all_parms = _jsonb_object_cat(j_all_parms, jsonb_build_object(
|
||||||
|
'login', f_getuser()
|
||||||
|
));
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if _try_integer(j_all_parms ->> 'rid_hub', 0) > 0
|
||||||
|
then
|
||||||
|
select *
|
||||||
|
from core.hub
|
||||||
|
where rid_hub = _try_integer(j_all_parms ->> 'rid_hub', 0)
|
||||||
|
into r_hub;
|
||||||
|
|
||||||
|
if r_hub.hubtype in ('client', 'lead')
|
||||||
|
then
|
||||||
|
j_all_parms = _jsonb_object_cat(j_all_parms, (
|
||||||
|
select jsonb_build_object(
|
||||||
|
'rid_adproclient', cli.rid_adproclient
|
||||||
|
, 'rid_client', cli.rid_adproclient
|
||||||
|
)
|
||||||
|
from t_adproclient cli
|
||||||
|
where cli.rid_hub = r_hub.rid_hub
|
||||||
|
limit 1
|
||||||
|
));
|
||||||
|
elsif r_hub.hubtype in ('trader')
|
||||||
|
then
|
||||||
|
j_all_parms = _jsonb_object_cat(j_all_parms, (
|
||||||
|
select jsonb_build_object(
|
||||||
|
'rid_trader', tdr.rid_trader
|
||||||
|
, 'rid_adprocreditor', tdr.rid_trader
|
||||||
|
)
|
||||||
|
from t_trader tdr
|
||||||
|
where tdr.rid_hub = r_hub.rid_hub
|
||||||
|
limit 1
|
||||||
|
));
|
||||||
|
|
||||||
|
end if;
|
||||||
|
|
||||||
|
end if;
|
||||||
|
|
||||||
|
j_all_parms = _jsonb_object_cat(j_all_parms, jsonb_build_object('AOP', null));
|
||||||
|
|
||||||
|
-- perform log_event(m_funcname, format('p_event_parameters=%s p_item_parameters=%s j_all_parms=%s', p_event_parameters::text,
|
||||||
|
-- p_item_parameters::text, j_all_parms::text), bt_enum('eventlog', 'local notice'));
|
||||||
|
|
||||||
|
if (
|
||||||
|
select 1
|
||||||
|
from core.actionfunction af
|
||||||
|
where
|
||||||
|
(
|
||||||
|
af.functionname = m_exec_funcname
|
||||||
|
or af.functionname = r_mastertaskitem.functionname_code
|
||||||
|
)
|
||||||
|
and af.fntype = 'pgsql-function'::citext
|
||||||
|
)
|
||||||
|
then
|
||||||
|
if m_exec_funcname = 'event_nextaction'
|
||||||
|
then
|
||||||
|
select r.p_retval, r.p_errmsg, r.p_data
|
||||||
|
from core.event_nextaction(j_all_parms) r
|
||||||
|
into m_retval,m_errmsg, j_output;
|
||||||
|
|
||||||
|
if m_retval > 0
|
||||||
|
then
|
||||||
|
p_retval = m_retval;
|
||||||
|
p_errmsg = m_errmsg;
|
||||||
|
|
||||||
|
select m.v[1]
|
||||||
|
from regexp_matches(p_errmsg, '\[([^\]]+)\]', 'ig') m(v)
|
||||||
|
where length(m.v[1]) between 2 and 10
|
||||||
|
order by m.v[1]
|
||||||
|
into p_errorcode;
|
||||||
|
|
||||||
|
return;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
elsif m_exec_funcname in ('event_pre_error_check')
|
||||||
|
then
|
||||||
|
|
||||||
|
select r.p_retval, r.p_errmsg, r.p_errorcode, r.p_parms
|
||||||
|
from core.event_pre_error_check(_jsonb_object_cat(j_all_parms, jsonb_build_object(
|
||||||
|
'rid_taskitem', r_taskitem.rid_taskitem
|
||||||
|
, 'rid_tasklist', r_taskitem.rid_tasklist
|
||||||
|
, 'rid_mastertaskitem', r_mastertaskitem.rid_mastertaskitem
|
||||||
|
))) r
|
||||||
|
into p_retval,p_errmsg, p_errorcode, p_output;
|
||||||
|
|
||||||
|
|
||||||
|
return;
|
||||||
|
elsif m_exec_funcname = 'ui_maint_commitem'
|
||||||
|
then
|
||||||
|
select r.p_retval, r.p_errmsg, r.p_options
|
||||||
|
from core.ui_maint_commitem(_jsonb_object_cat(
|
||||||
|
r_taskitem.jsonvalue
|
||||||
|
, r_taskitemevent.jsonvalue
|
||||||
|
, jsonb_build_object(
|
||||||
|
'rid_taskitem', r_taskitem.rid_taskitem,
|
||||||
|
'rid_mastertaskitem', r_taskitem.rid_mastertaskitem,
|
||||||
|
'rid_hub_user', j_all_parms ->> 'rid_hub_user',
|
||||||
|
'rid_hub_payload', _try_integers(j_all_parms ->> 'rid_hub', j_all_parms ->> 'rid_hub_payload')
|
||||||
|
)
|
||||||
|
)) r
|
||||||
|
into m_retval,m_errmsg, j_output;
|
||||||
|
|
||||||
|
if m_retval > 0
|
||||||
|
then
|
||||||
|
p_retval = m_retval;
|
||||||
|
p_errmsg = m_errmsg;
|
||||||
|
|
||||||
|
select m.v[1]
|
||||||
|
from regexp_matches(p_errmsg, '\[([^\]]+)\]', 'ig') m(v)
|
||||||
|
where length(m.v[1]) between 2 and 10
|
||||||
|
order by m.v[1]
|
||||||
|
into p_errorcode;
|
||||||
|
|
||||||
|
return;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
elsif m_exec_funcname = 'ui_modify_paymentplan'
|
||||||
|
then
|
||||||
|
|
||||||
|
select r.p_retval, r.p_errmsg, r.p_data
|
||||||
|
from ui_modify_paymentplan(j_all_parms) r
|
||||||
|
into m_retval,m_errmsg, j_output;
|
||||||
|
|
||||||
|
if m_retval > 0
|
||||||
|
then
|
||||||
|
p_retval = m_retval;
|
||||||
|
p_errmsg = m_errmsg;
|
||||||
|
select m.v[1]
|
||||||
|
from regexp_matches(p_errmsg, '\[([^\]]+)\]', 'ig') m(v)
|
||||||
|
where length(m.v[1]) between 2 and 10
|
||||||
|
order by m.v[1]
|
||||||
|
into p_errorcode;
|
||||||
|
|
||||||
|
|
||||||
|
return;
|
||||||
|
end if;
|
||||||
|
elsif m_exec_funcname in ('f_checkforreapprove_client')
|
||||||
|
then
|
||||||
|
select f_checkforreapprove_client(cli.rid_adproclient)
|
||||||
|
from t_adproclient cli
|
||||||
|
where cli.rid_hub = r_taskitem.rid_hub
|
||||||
|
into m_retval;
|
||||||
|
|
||||||
|
if m_retval > 0
|
||||||
|
then
|
||||||
|
p_retval = 1;
|
||||||
|
p_errmsg = 'Re-approval is required for this client and it needs to be uploaded to the PDA.';
|
||||||
|
p_errorcode = 'CLIAPRV1';
|
||||||
|
p_output = jsonb_build_object('message', p_errmsg, 'actionstatus', 'error',
|
||||||
|
'errorcode', p_errorcode
|
||||||
|
, 'alerttype', 'error'
|
||||||
|
, 'in_modal', true
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
else
|
||||||
|
p_retval = 0;
|
||||||
|
p_errmsg = '';
|
||||||
|
p_errorcode = '';
|
||||||
|
p_output = jsonb_build_object('actionstatus', 'success', 'delete_after', true);
|
||||||
|
return;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
elsif m_exec_funcname in ('c_checkforreapprove')
|
||||||
|
then
|
||||||
|
select c_checkforreapprove(cli.rid_adproclient)
|
||||||
|
from t_adproclient cli
|
||||||
|
where cli.rid_hub = r_taskitem.rid_hub
|
||||||
|
into m_retval;
|
||||||
|
|
||||||
|
if m_retval > 0
|
||||||
|
then
|
||||||
|
p_retval = 1;
|
||||||
|
p_errmsg = 'Re-approval is required for this client`s payment plan and it needs to be uploaded to the PDA.';
|
||||||
|
p_errorcode = 'CLIAPRV2';
|
||||||
|
p_output = jsonb_build_object('message', p_errmsg, 'actionstatus', 'error', 'errorcode'
|
||||||
|
, p_errorcode
|
||||||
|
, 'alerttype', 'error'
|
||||||
|
, 'in_modal', true);
|
||||||
|
|
||||||
|
return;
|
||||||
|
else
|
||||||
|
p_retval = 0;
|
||||||
|
p_errmsg = '';
|
||||||
|
p_errorcode = '';
|
||||||
|
p_output = jsonb_build_object('actionstatus', 'success', 'delete_after', true);
|
||||||
|
return;
|
||||||
|
end if;
|
||||||
|
elsif m_exec_funcname in ('action_init_hublinks')
|
||||||
|
then
|
||||||
|
|
||||||
|
select r.p_retval, r.p_errmsg
|
||||||
|
from core.action_init_hublinks(_jsonb_object_cat(p_item_parameters, jsonb_build_object(
|
||||||
|
'rid_taskitem', r_taskitem.rid_taskitem,
|
||||||
|
'rid_mastertaskitem', r_taskitem.rid_mastertaskitem,
|
||||||
|
'rid_hub_user', j_all_parms ->> 'rid_hub_user',
|
||||||
|
'event_status', r_taskitemevent.status,
|
||||||
|
'parent_rid_taskitemevent', r_taskitemevent.rid_taskitemevent,
|
||||||
|
'calling_function', m_funcname
|
||||||
|
))) r
|
||||||
|
into m_retval, m_errmsg;
|
||||||
|
|
||||||
|
if m_retval > 0
|
||||||
|
then
|
||||||
|
p_retval = m_retval;
|
||||||
|
p_errmsg = m_errmsg;
|
||||||
|
|
||||||
|
select m.v[1]
|
||||||
|
from regexp_matches(p_errmsg, '\[([^\]]+)\]', 'ig') m(v)
|
||||||
|
where length(m.v[1]) between 2 and 10
|
||||||
|
order by m.v[1]
|
||||||
|
into p_errorcode;
|
||||||
|
|
||||||
|
return;
|
||||||
|
end if;
|
||||||
|
elsif m_exec_funcname in ('ui_clientscenario_commit')
|
||||||
|
then
|
||||||
|
|
||||||
|
if coalesce(_try_integers(j_all_parms ->> 'rid_clientscenario', j_all_parms ->> 'p_rid_clientscenario'), 0) = 0
|
||||||
|
then
|
||||||
|
select s.rid_clientscenario
|
||||||
|
from clientscenario s
|
||||||
|
inner join t_adproclient c on c.rid_adproclient = s.rid_adproclient
|
||||||
|
and c.rid_hub = r_taskitem.rid_hub
|
||||||
|
where s.status in (1, 2)
|
||||||
|
order by s.status, s.rid_clientscenario desc
|
||||||
|
limit 1
|
||||||
|
into m_rid;
|
||||||
|
|
||||||
|
if nv(m_rid) = 0 and r_taskitem.rid_hub_link > 0
|
||||||
|
then
|
||||||
|
--Falback to link parent client when tasklink is for client.
|
||||||
|
select s.rid_clientscenario
|
||||||
|
from core.hub_link h
|
||||||
|
inner join t_adproclient c on c.rid_hub = h.rid_hub_parent
|
||||||
|
and h.parent_hubtype = 'client'
|
||||||
|
inner join clientscenario s on s.rid_adproclient = c.rid_adproclient
|
||||||
|
and s.status in (1, 2)
|
||||||
|
where h.rid_hub_link = r_taskitem.rid_hub_link
|
||||||
|
order by s.status, s.rid_clientscenario desc
|
||||||
|
limit 1
|
||||||
|
into m_rid;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
j_all_parms = _jsonb_object_cat(j_all_parms, jsonb_build_object(
|
||||||
|
'rid_clientscenario', m_rid
|
||||||
|
));
|
||||||
|
|
||||||
|
end if;
|
||||||
|
|
||||||
|
select r.p_retval, r.p_errmsg
|
||||||
|
from public.ui_clientscenario_commit(_try_integers(j_all_parms ->> 'rid_clientscenario'
|
||||||
|
, j_all_parms ->> 'p_rid_clientscenario', null)
|
||||||
|
, null
|
||||||
|
, _jsonb_object_cat(p_item_parameters, jsonb_build_object(
|
||||||
|
'rid_taskitem', r_taskitem.rid_taskitem,
|
||||||
|
'rid_hub', r_taskitem.rid_hub,
|
||||||
|
'rid_mastertaskitem', r_taskitem.rid_mastertaskitem,
|
||||||
|
'rid_hub_user', j_all_parms ->> 'rid_hub_user',
|
||||||
|
'event_status', r_taskitemevent.status,
|
||||||
|
'parent_rid_taskitemevent', r_taskitemevent.rid_taskitemevent,
|
||||||
|
'calling_function', m_funcname
|
||||||
|
))) r
|
||||||
|
into m_retval, m_errmsg;
|
||||||
|
|
||||||
|
if p_event_parameters ->> 'no_error' in ('1', 'true') or p_item_parameters ->> 'no_error' in ('1', 'true')
|
||||||
|
then
|
||||||
|
select m.v[1]
|
||||||
|
from regexp_matches(p_errmsg, '\[([^\]]+)\]', 'ig') m(v)
|
||||||
|
where length(m.v[1]) between 2 and 10
|
||||||
|
order by m.v[1]
|
||||||
|
into p_errorcode;
|
||||||
|
|
||||||
|
return;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if m_retval > 0
|
||||||
|
then
|
||||||
|
p_retval = m_retval;
|
||||||
|
p_errmsg = m_errmsg;
|
||||||
|
select m.v[1]
|
||||||
|
from regexp_matches(p_errmsg, '\[([^\]]+)\]', 'ig') m(v)
|
||||||
|
where length(m.v[1]) between 2 and 10
|
||||||
|
order by m.v[1]
|
||||||
|
into p_errorcode;
|
||||||
|
|
||||||
|
return;
|
||||||
|
end if;
|
||||||
|
elsif exists (
|
||||||
|
select ns.nspname, pro.*
|
||||||
|
from pg_proc pro
|
||||||
|
inner join pg_namespace ns on ns.oid = pro.pronamespace
|
||||||
|
and ns.nspname not in ('postgres', 'pg_catalog')
|
||||||
|
and ns.nspname = m_exec_schema
|
||||||
|
where pro.proname = m_exec_funcname
|
||||||
|
)
|
||||||
|
and m_exec_funcname in
|
||||||
|
('clfrm_builderrorlist', 'upl_builduploaderrors', 'clfrm_propcalc_errlist', 'ui_clientscenarion_errorlist')
|
||||||
|
then
|
||||||
|
raise notice 'Running %',m_exec_funcname;
|
||||||
|
|
||||||
|
p_retval = 0;
|
||||||
|
p_errmsg = '';
|
||||||
|
m_errdetail = '';
|
||||||
|
SET session_replication_role = DEFAULT;
|
||||||
|
|
||||||
|
if not exists (
|
||||||
|
select cli.rid_adproclient
|
||||||
|
from t_adproclient cli
|
||||||
|
where cli.rid_hub = r_taskitem.rid_hub
|
||||||
|
)
|
||||||
|
then
|
||||||
|
p_retval = 1;
|
||||||
|
p_errmsg = 'No client found for the hub.';
|
||||||
|
return;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
with
|
||||||
|
cli as (
|
||||||
|
select cli.rid_adproclient
|
||||||
|
from t_adproclient cli
|
||||||
|
where cli.rid_hub = r_taskitem.rid_hub
|
||||||
|
)
|
||||||
|
, src as (
|
||||||
|
select o.errordescription as errordescription
|
||||||
|
, o.errorcode as errorcode
|
||||||
|
, o.error_priority as priority
|
||||||
|
, o.errortype
|
||||||
|
from clfrm_builderrorlist((
|
||||||
|
select cli.rid_adproclient
|
||||||
|
from cli
|
||||||
|
limit 1
|
||||||
|
), 0, 0) o
|
||||||
|
where m_exec_funcname in ('clfrm_builderrorlist')
|
||||||
|
union
|
||||||
|
select o.errordescription as errordescription
|
||||||
|
, o.errorcode as errorcode
|
||||||
|
, o.error_priority as priority
|
||||||
|
, o.errortype
|
||||||
|
from clfrm_builderrorlist((
|
||||||
|
select cli.rid_adproclient
|
||||||
|
from cli
|
||||||
|
limit 1
|
||||||
|
), 0, 1) o
|
||||||
|
where m_exec_funcname in ('clfrm_builderrorlist')
|
||||||
|
union
|
||||||
|
select o.errordescription as errordescription, o.errorcode as errorcode, 1 as priority, 'error' as errortype
|
||||||
|
from clfrm_propcalc_errlist((
|
||||||
|
select cli.rid_adproclient
|
||||||
|
from cli
|
||||||
|
limit 1
|
||||||
|
)) o
|
||||||
|
where m_exec_funcname = 'm_exec_funcname'
|
||||||
|
|
||||||
|
union
|
||||||
|
select o.errdescription as errordescription
|
||||||
|
, o.errorcode as errorcode
|
||||||
|
, o.severity as priority
|
||||||
|
, o.errtype as errortype
|
||||||
|
from upl_builduploaderrors((
|
||||||
|
select cli.rid_adproclient
|
||||||
|
from cli
|
||||||
|
limit 1
|
||||||
|
)
|
||||||
|
, _bv(j_all_parms ->> 'p_approve_mode', j_all_parms ->> 'approve_mode', j_all_parms ->> 'mode', 'normal')) o
|
||||||
|
where
|
||||||
|
m_exec_funcname in ('upl_builduploaderrors')
|
||||||
|
union
|
||||||
|
select elm.j ->> 'errordescription' as errordescription
|
||||||
|
, elm.j ->> 'errorcode' as errorcode
|
||||||
|
, _try_integer(elm.j ->> 'priority', 1) as priority
|
||||||
|
, _bv(elm.j ->> 'errortype', 'error') as errortype
|
||||||
|
from ui_clientscenarion_errorlist((
|
||||||
|
select sc.rid_clientscenario
|
||||||
|
from cli
|
||||||
|
inner join clientscenario sc on sc.rid_adproclient = cli.rid_adproclient
|
||||||
|
and sc.status = 1
|
||||||
|
order by sc.rid_clientscenario desc
|
||||||
|
limit 1
|
||||||
|
)) o
|
||||||
|
cross join jsonb_array_elements(case
|
||||||
|
when jsonb_typeof(o.p_data) = 'object' then jsonb_build_array(o.p_data)
|
||||||
|
when jsonb_typeof(o.p_data) = 'array' then o.p_data
|
||||||
|
else jsonb_build_array()
|
||||||
|
end) elm(j)
|
||||||
|
where m_exec_funcname in ('ui_clientscenarion_errorlist')
|
||||||
|
order by priority
|
||||||
|
)
|
||||||
|
, composed as (
|
||||||
|
select
|
||||||
|
-- src.errorcode
|
||||||
|
-- , src.errordescription
|
||||||
|
-- , src.priority as errorpriority
|
||||||
|
-- , src.errortype
|
||||||
|
row_number() over (partition by src.errorcode, src.errordescription order by src.priority desc) as rn
|
||||||
|
, evt2.rid_mastertaskitemevent as rid_mastertaskitemevent
|
||||||
|
, mti.rid_mastertaskitem as rid_mastertaskitem
|
||||||
|
, ti.rid_taskitem as rid_taskitem
|
||||||
|
, core.f_get_user_hub_rid() as rid_hub_user
|
||||||
|
, jsonb_build_object(
|
||||||
|
'errordescription', src.errordescription
|
||||||
|
, 'errorcode', src.errorcode
|
||||||
|
, 'errortype', src.errortype
|
||||||
|
, 'errorpriority', src.priority
|
||||||
|
) as jsonvalue
|
||||||
|
from src
|
||||||
|
--Get the main linked event
|
||||||
|
left outer join core.mastertaskitemevent ev1 on ev1.rid_mastertaskitem = r_taskitem.rid_mastertaskitem
|
||||||
|
and nv(ev1.errorcode) <> ''
|
||||||
|
and (
|
||||||
|
ev1.errorcode = src.errorcode
|
||||||
|
or ev1.errorcode in (
|
||||||
|
select m.interface_code
|
||||||
|
from core.error_code_map(jsonb_build_object('filter_code', src.errorcode)) m
|
||||||
|
where nv(m.interface_code) <> ''
|
||||||
|
)
|
||||||
|
)
|
||||||
|
left outer join core.mastertaskitemeventreaction er1
|
||||||
|
on er1.rid_mastertaskitemevent = ev1.rid_mastertaskitemevent
|
||||||
|
-- Fallback to the global event list
|
||||||
|
left outer join core.mastertaskitemevent evt2 on (
|
||||||
|
evt2.rid_mastertaskitemevent =
|
||||||
|
er1.rid_mastertaskitemevent_target
|
||||||
|
or evt2.status in (2)
|
||||||
|
and nv(evt2.errorcode) <> ''
|
||||||
|
and (
|
||||||
|
evt2.errorcode = src.errorcode
|
||||||
|
or evt2.errorcode in (
|
||||||
|
select m.interface_code
|
||||||
|
from core.error_code_map(jsonb_build_object('filter_code', src.errorcode)) m
|
||||||
|
where nv(m.interface_code) <> ''
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
and nv(evt2.inactive) = 0
|
||||||
|
left outer join core.mastertaskitem mti on mti.rid_mastertaskitem = evt2.rid_mastertaskitem
|
||||||
|
left outer join core.taskitem ti on ti.rid_mastertaskitem = mti.rid_mastertaskitem
|
||||||
|
and ti.rid_hub = r_taskitem.rid_hub
|
||||||
|
and nv(ti.rid_hub_link) = nv(r_taskitem.rid_hub_link)
|
||||||
|
and ti.status in (1, 2, 3)
|
||||||
|
)
|
||||||
|
select jsonb_agg(_jsonb_object_cat(to_jsonb(mti), to_jsonb(ti), jsonb_strip_nulls(to_jsonb(c))))
|
||||||
|
filter ( where c.rn = 1 )
|
||||||
|
, count(1)
|
||||||
|
from composed c
|
||||||
|
left outer join core.mastertaskitem mti on mti.rid_mastertaskitem = c.rid_mastertaskitem
|
||||||
|
left outer join core.taskitem ti on ti.rid_taskitem = c.rid_taskitem
|
||||||
|
into j_tmp, m_retval;
|
||||||
|
|
||||||
|
if m_retval > 0
|
||||||
|
then
|
||||||
|
-- update core.taskitem u
|
||||||
|
-- set jsonvalue = jsonb_build_object('errorlist', j_tmp)
|
||||||
|
-- where u.rid_taskitem = r_taskitem.rid_taskitem;
|
||||||
|
|
||||||
|
p_output =
|
||||||
|
jsonb_build_object('actionstatus', 'error', 'errorcode', 'ERRLIST', 'errorlist', j_tmp, 'delete_caller',
|
||||||
|
true);
|
||||||
|
else
|
||||||
|
p_output =
|
||||||
|
jsonb_build_object('message', null, 'actionstatus', 'success', 'errorcode', null, 'delete_after', true,
|
||||||
|
'errorlist', null);
|
||||||
|
|
||||||
|
end if;
|
||||||
|
|
||||||
|
perform log_event(m_funcname, format(E'List Function (%s) Results Code=%s Error=%s.
|
||||||
|
MasterTaskItem:%s \n p_output:%s \nm_errdetail:%s'
|
||||||
|
, m_exec_funcname
|
||||||
|
, p_errorcode
|
||||||
|
, p_errmsg
|
||||||
|
, r_mastertaskitem.description
|
||||||
|
, p_output
|
||||||
|
, m_errdetail
|
||||||
|
)
|
||||||
|
, bt_enum('eventlog', 'local error'));
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
--m_retval =0;
|
||||||
|
|
||||||
|
elsif exists (
|
||||||
|
select ns.nspname, pro.*
|
||||||
|
from pg_proc pro
|
||||||
|
inner join pg_namespace ns on ns.oid = pro.pronamespace
|
||||||
|
and ns.nspname not in ('postgres', 'pg_catalog')
|
||||||
|
and ns.nspname = m_exec_schema
|
||||||
|
where pro.proname = m_exec_funcname
|
||||||
|
)
|
||||||
|
then
|
||||||
|
with
|
||||||
|
args as (
|
||||||
|
select ns.nspname
|
||||||
|
, pro.proname
|
||||||
|
, t.typname
|
||||||
|
, i.i
|
||||||
|
, pro.proargnames[i - 1] as argname
|
||||||
|
, pro.proargmodes[i - 1] as mode
|
||||||
|
, CASE
|
||||||
|
WHEN pro.proargdefaults IS NOT NULL
|
||||||
|
AND i.i - 1 > (pro.pronargs - pro.pronargdefaults)
|
||||||
|
THEN split_part(
|
||||||
|
pg_get_expr(pro.proargdefaults, 0),
|
||||||
|
',',
|
||||||
|
i.i - 1 - (pro.pronargs - pro.pronargdefaults)
|
||||||
|
)::text
|
||||||
|
ELSE NULL::text
|
||||||
|
END as default_value
|
||||||
|
, row_number() over (order by i.i) as number
|
||||||
|
--,pro.*
|
||||||
|
from pg_proc pro
|
||||||
|
inner join pg_namespace ns on ns.oid = pro.pronamespace
|
||||||
|
and ns.nspname not in ('postgres', 'pg_catalog')
|
||||||
|
JOIN LATERAL generate_subscripts(pro.proallargtypes, 1) AS i ON true
|
||||||
|
JOIN pg_type t ON t.oid = pro.proallargtypes[i - 1]
|
||||||
|
where
|
||||||
|
pro.proname = m_exec_funcname
|
||||||
|
and ns.nspname = m_exec_schema
|
||||||
|
--pro.proname in ('hsync_upload_clienttohyphen')
|
||||||
|
AND (pro.proargmodes IS NULL OR pro.proargmodes[i - 1] in ('i', 'b'))
|
||||||
|
)
|
||||||
|
, argval as (
|
||||||
|
select args.*
|
||||||
|
--, _bv(j_all_parms ->> args.argname,args.default_value) as argvalue
|
||||||
|
, case
|
||||||
|
when nv(j_all_parms ->> args.argname) in ('', '0', 'null')
|
||||||
|
and args.argname ilike 'p_%' and
|
||||||
|
nv(j_all_parms ->> replace(args.argname, 'p_', '')) not in ('', '0', 'null')
|
||||||
|
then j_all_parms ->> replace(args.argname, 'p_', '')
|
||||||
|
|
||||||
|
when nv(j_all_parms ->> args.argname) <> '' then j_all_parms ->> args.argname
|
||||||
|
when args.argname ilike 'p_%' and nv(j_all_parms ->> replace(args.argname, 'p_', '')) <> ''
|
||||||
|
then j_all_parms ->> replace(args.argname, 'p_', '')
|
||||||
|
when args.argname ilike 'p_login%' and j_all_parms ->> 'login' is not null
|
||||||
|
then j_all_parms ->> 'login'
|
||||||
|
when args.argname ilike 'p_user%' and j_all_parms ->> 'login' is not null
|
||||||
|
then j_all_parms ->> 'login'
|
||||||
|
|
||||||
|
when nv(args.default_value) <> '' then args.default_value
|
||||||
|
else null::text
|
||||||
|
end as argvalue
|
||||||
|
from args
|
||||||
|
|
||||||
|
)
|
||||||
|
select (
|
||||||
|
select string_agg(format('%s::%s', case
|
||||||
|
when a.typname ilike '%int%' then a.argvalue
|
||||||
|
else quote_literal(a.argvalue)
|
||||||
|
end, a.typname), ', ') as args
|
||||||
|
from argval a
|
||||||
|
)
|
||||||
|
, (
|
||||||
|
select count(1)
|
||||||
|
from argval a
|
||||||
|
where a.argvalue is null
|
||||||
|
)
|
||||||
|
, (
|
||||||
|
select format('[ECR0002] Cannot execute function %s. The following arguments are missing: %s (Defaults: %s) ',
|
||||||
|
m_exec_funcname, string_agg(a.argname, ','), string_agg(a.default_value, ','))
|
||||||
|
from argval a
|
||||||
|
where a.argvalue is null
|
||||||
|
)
|
||||||
|
into m_sql,m_retval, m_errmsg, m_errcode;
|
||||||
|
|
||||||
|
raise notice 'Function % executed with arguments: %', m_exec_funcname, m_sql;
|
||||||
|
|
||||||
|
if m_retval > 0
|
||||||
|
then
|
||||||
|
p_retval = 1;
|
||||||
|
p_errmsg = m_errmsg;
|
||||||
|
select m.v[1]
|
||||||
|
from regexp_matches(p_errmsg, '\[([^\]]+)\]', 'ig') m(v)
|
||||||
|
where length(m.v[1]) between 2 and 10
|
||||||
|
order by m.v[1]
|
||||||
|
into p_errorcode;
|
||||||
|
|
||||||
|
perform log_event(m_funcname, format(
|
||||||
|
E'Error on Function (%s) Code=%s Error=%s. MasterTaskItem:%s \n SQL:%s \n j_all_parms:%s \n Controls: %s'
|
||||||
|
, m_exec_funcname
|
||||||
|
, p_errorcode
|
||||||
|
, p_errmsg
|
||||||
|
, r_mastertaskitem.description
|
||||||
|
, m_sql
|
||||||
|
, substr((j_all_parms || jsonb_build_object('AOP', null))::text, 1, 100000)
|
||||||
|
, j_controls::text
|
||||||
|
)
|
||||||
|
, bt_enum('eventlog', 'local error'));
|
||||||
|
|
||||||
|
return;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
m_sql = format('select to_jsonb(r) as val from %s.%s(%s) r ;', m_exec_schema, m_exec_funcname, m_sql);
|
||||||
|
|
||||||
|
perform log_event(m_funcname, format('Executing Function (%s) with SQL: %s', m_exec_funcname, m_sql),
|
||||||
|
bt_enum('eventlog', 'local notice'));
|
||||||
|
|
||||||
|
if m_exec_funcname::citext = any (a_broker_procs)
|
||||||
|
then
|
||||||
|
select r.p_retval, r.p_errmsg
|
||||||
|
from agent_job_add(format('exec %s', m_sql)
|
||||||
|
, get_agent_freequeue(3)
|
||||||
|
, format('%s(taskitemevent=%s)', m_exec_funcname, r_taskitemevent.rid_taskitemevent)
|
||||||
|
, 15
|
||||||
|
) r
|
||||||
|
into m_retval,m_errmsg;
|
||||||
|
|
||||||
|
elsif m_exec_funcname::citext = any (a_async_procs)
|
||||||
|
then
|
||||||
|
select _bv(j.val -> 'val', j.val)
|
||||||
|
from autoexec_query(m_sql, jsonb_build_object()) j(val)
|
||||||
|
into j_output;
|
||||||
|
|
||||||
|
else
|
||||||
|
execute m_sql into j_output;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
p_output = j_output;
|
||||||
|
|
||||||
|
if p_event_parameters ->> 'no_error' in ('1', 'true') or p_item_parameters ->> 'no_error' in ('1', 'true')
|
||||||
|
then
|
||||||
|
select m.v[1]
|
||||||
|
from regexp_matches(p_errmsg, '\[([^\]]+)\]', 'ig') m(v)
|
||||||
|
where length(m.v[1]) between 2 and 10
|
||||||
|
order by m.v[1]
|
||||||
|
into p_errorcode;
|
||||||
|
|
||||||
|
perform log_event(m_funcname, format('Error(!) on Function (%s) Code=%s Error=%s. MasterTaskItem:%s Response:%s'
|
||||||
|
, m_exec_funcname
|
||||||
|
, p_errorcode
|
||||||
|
, p_errmsg
|
||||||
|
, r_mastertaskitem.description
|
||||||
|
, substr(j_output::text, 1, 10000)
|
||||||
|
)
|
||||||
|
, bt_enum('eventlog', 'local error'));
|
||||||
|
|
||||||
|
return;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if jsonb_typeof(j_output) in ('number', 'boolean', 'string')
|
||||||
|
then
|
||||||
|
perform log_event(m_funcname,
|
||||||
|
format('Function (%s) returned scalar value: %s', m_exec_funcname, j_output::text),
|
||||||
|
bt_enum('eventlog', 'debug'));
|
||||||
|
|
||||||
|
if j_all_parms -> 'p_output' is not null
|
||||||
|
and (j_all_parms ->> 'p_output')::citext is distinct from j_output::citext
|
||||||
|
then
|
||||||
|
p_retval = 1;
|
||||||
|
p_errmsg =
|
||||||
|
'Output value does not match expected value. Expected: ' || j_all_parms ->> 'p_output' || ' Actual: ' ||
|
||||||
|
j_output::text;
|
||||||
|
return;
|
||||||
|
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if _try_integer(j_output ->> 'p_retval', 0) > 0
|
||||||
|
and m_exec_funcname::citext <> all (a_noerror_procs)
|
||||||
|
then
|
||||||
|
p_retval = _try_integer(j_output ->> 'p_retval', 0);
|
||||||
|
p_errmsg = format('%s', j_output ->> 'p_errmsg');
|
||||||
|
p_errorcode = _bv(j_output ->> 'p_error_code', j_output ->> 'p_errorcode', '');
|
||||||
|
|
||||||
|
|
||||||
|
perform log_event(m_funcname, format('Error on Function (%s) Code=%s Error=%s. MasterTaskItem:%s Response:%s'
|
||||||
|
, m_exec_funcname
|
||||||
|
, p_errorcode
|
||||||
|
, p_errmsg
|
||||||
|
, r_mastertaskitem.description
|
||||||
|
, substr(j_output::text, 1, 10000)
|
||||||
|
)
|
||||||
|
, bt_enum('eventlog', 'local error'));
|
||||||
|
|
||||||
|
|
||||||
|
if nv(p_errorcode) = ''
|
||||||
|
then
|
||||||
|
select m.v[1]
|
||||||
|
from regexp_matches(p_errmsg, '\[([^\]]+)\]', 'ig') m(v)
|
||||||
|
where length(m.v[1]) between 2 and 10
|
||||||
|
order by m.v[1]
|
||||||
|
into p_errorcode;
|
||||||
|
|
||||||
|
if nv(p_errorcode) = ''
|
||||||
|
then
|
||||||
|
p_errorcode = 'ECR0003';
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
return;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
else
|
||||||
|
perform log_event(m_funcname, format('Function (%s) not defined for this event (%s).',
|
||||||
|
p_event_parameters -> 'AFN_RID_ACTIONFUNCTION_CODE' ->> 'functionname'
|
||||||
|
, r_mastertaskitem.description)
|
||||||
|
, bt_enum('eventlog', 'local error'));
|
||||||
|
|
||||||
|
select m.v[1]
|
||||||
|
from regexp_matches(p_errmsg, '\[([^\]]+)\]', 'ig') m(v)
|
||||||
|
where length(m.v[1]) between 2 and 10
|
||||||
|
order by m.v[1]
|
||||||
|
into p_errorcode;
|
||||||
|
|
||||||
|
return;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
|
||||||
|
else
|
||||||
|
raise notice 'Type not supported: %', p_event_parameters -> 'AFN_RID_ACTIONFUNCTION_CODE' ->> 'fntype';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
--select * from core.actionfunction
|
||||||
|
if nv(p_errorcode) = '' and nv(p_errmsg) <> ''
|
||||||
|
then
|
||||||
|
select m.v[1]
|
||||||
|
from regexp_matches(p_errmsg, '\[([^\]]+)\]', 'ig') m(v)
|
||||||
|
where length(m.v[1]) between 2 and 10
|
||||||
|
order by m.v[1]
|
||||||
|
into p_errorcode;
|
||||||
|
|
||||||
|
if nv(p_errorcode) = ''
|
||||||
|
then
|
||||||
|
p_errorcode = 'PRCERR';
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
EXCEPTION
|
||||||
|
WHEN others THEN
|
||||||
|
GET STACKED DIAGNOSTICS
|
||||||
|
m_errmsg = MESSAGE_TEXT
|
||||||
|
,m_errcontext = PG_EXCEPTION_CONTEXT
|
||||||
|
,m_errdetail = PG_EXCEPTION_DETAIL
|
||||||
|
,m_errhint = PG_EXCEPTION_HINT
|
||||||
|
,m_errstate = RETURNED_SQLSTATE;
|
||||||
|
|
||||||
|
p_errmsg := get_err_msg(m_funcname, m_errmsg, m_errcontext, m_errdetail, m_errhint, m_errstate);
|
||||||
|
p_retval = 1;
|
||||||
|
if nv(p_errorcode) = '' and nv(p_errmsg) <> ''
|
||||||
|
then
|
||||||
|
select m.v[1]
|
||||||
|
from regexp_matches(p_errmsg, '\[([^\]]+)\]', 'ig') m(v)
|
||||||
|
where length(m.v[1]) between 2 and 10
|
||||||
|
order by m.v[1]
|
||||||
|
into p_errorcode;
|
||||||
|
|
||||||
|
if nv(p_errorcode) = ''
|
||||||
|
then
|
||||||
|
p_errorcode = 'PRCERR';
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
--Tests
|
||||||
|
/*
|
||||||
|
select (regexp_matches('[ECR0001] No AFN inside the AOP pack.', '\[([^\]]+)\]'))[1]
|
||||||
|
, (regexp_matches('[21] No AFN inside the AOP pack. [45345]', '\[([^\]]+)\]'))[1]
|
||||||
|
, (regexp_matches('A Test', '\[([^\]]+)\]'))[1]
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
select *
|
||||||
|
from core.taskitemevent e
|
||||||
|
where e.rid_taskitem = 26344
|
||||||
|
limit
|
||||||
|
;*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
select *
|
||||||
|
from core.event_exec_func((
|
||||||
|
select _jsonb_object_cat( to_jsonb(e)
|
||||||
|
,jsonb_build_object('AFN_RID_ACTIONFUNCTION_CODE',to_jsonb(af)))
|
||||||
|
from core.taskitemevent e
|
||||||
|
inner join core.taskitem ti on ti.rid_taskitem = e.rid_taskitem
|
||||||
|
inner join core.actionoption ao on ao.guid = ti.jsonvalue -> 'AOP' ->> 'guid'
|
||||||
|
inner join core.actionfunction af on af.rid_actionfunction = ao.rid_actionfunction_code
|
||||||
|
where e.rid_taskitem = 26344
|
||||||
|
),(select _jsonb_object_cat( to_jsonb(e)
|
||||||
|
,jsonb_build_object('AFN_RID_ACTIONFUNCTION_CODE',to_jsonb(af)))
|
||||||
|
from core.taskitem e
|
||||||
|
inner join core.mastertaskitem mti
|
||||||
|
on mti.rid_mastertaskitem = e.rid_mastertaskitem
|
||||||
|
inner join core.actionoption ao on ao.guid = e.jsonvalue -> 'AOP' ->> 'guid'
|
||||||
|
inner join core.actionfunction af on af.rid_actionfunction = ao.rid_actionfunction_code
|
||||||
|
where e.rid_taskitem = 26344
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
selecT * from v_eventlog
|
||||||
|
|
||||||
|
select * from core.taskitem ti
|
||||||
|
order by ti.rid_taskitem desc
|
||||||
|
|
||||||
|
*/
|
||||||
+1105
File diff suppressed because it is too large
Load Diff
+613
@@ -0,0 +1,613 @@
|
|||||||
|
--select * from dropall('ui_action_event','core');
|
||||||
|
CREATE OR REPLACE FUNCTION core.ui_action_event(
|
||||||
|
p_payload INOUT jsonb
|
||||||
|
, OUT p_retval integer
|
||||||
|
, OUT p_errmsg text
|
||||||
|
)
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
VOLATILE
|
||||||
|
SECURITY DEFINER
|
||||||
|
AS
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
--Error Handling--
|
||||||
|
m_funcname text = 'core.ui_action_event';
|
||||||
|
m_errmsg text;
|
||||||
|
m_errcontext text;
|
||||||
|
m_errdetail text;
|
||||||
|
m_errhint text;
|
||||||
|
m_errstate text;
|
||||||
|
m_retval integer;
|
||||||
|
--Error Handling--
|
||||||
|
m_rid_hub integer;
|
||||||
|
m_rid_hub_link integer;
|
||||||
|
m_rid_hub_user integer;
|
||||||
|
m_temp_rid integer;
|
||||||
|
m_final_rid_taskitem integer;
|
||||||
|
m_temp_rid_list integer[];
|
||||||
|
m_status integer;
|
||||||
|
m_outcome integer;
|
||||||
|
r_mastertaskitemevent core.mastertaskitemevent%rowtype;
|
||||||
|
r_taskitem core.taskitem%rowtype;
|
||||||
|
r_taskitemevent core.taskitemevent%rowtype;
|
||||||
|
r_mastertaskitem core.mastertaskitem%rowtype;
|
||||||
|
r_mastertask core.mastertask%rowtype;
|
||||||
|
r_lp record;
|
||||||
|
m_check_error boolean := false;
|
||||||
|
m_cancel boolean := false;
|
||||||
|
j_result jsonb;
|
||||||
|
j_obj jsonb;
|
||||||
|
m_tm timestamp ;
|
||||||
|
BEGIN
|
||||||
|
m_tm = clock_timestamp();
|
||||||
|
p_retval = 0;
|
||||||
|
p_errmsg = '';
|
||||||
|
j_result = jsonb_build_array();
|
||||||
|
|
||||||
|
m_rid_hub = _try_integer(p_payload ->> 'rid_hub', 0);
|
||||||
|
m_rid_hub_user = _try_integer(p_payload ->> 'rid_hub_user', core.f_get_user_hub_rid());
|
||||||
|
m_status = _try_integer(p_payload ->> 'status', 0);
|
||||||
|
m_outcome = _try_integer(p_payload ->> 'outcome', 0);
|
||||||
|
m_check_error = (p_payload ->> 'check_error')::citext in ('1', 'true');
|
||||||
|
m_cancel = (p_payload ->> 'cancel')::citext in ('1', 'true');
|
||||||
|
|
||||||
|
select *
|
||||||
|
from core.mastertaskitemevent mte
|
||||||
|
where mte.rid_mastertaskitemevent = _try_integer(p_payload ->> 'rid_mastertaskitemevent', 0)
|
||||||
|
into r_mastertaskitemevent;
|
||||||
|
|
||||||
|
select mti.*
|
||||||
|
from core.mastertaskitem mti
|
||||||
|
where
|
||||||
|
mti.rid_mastertaskitem = _try_integer(p_payload ->> 'rid_mastertaskitem', 0)
|
||||||
|
and _try_integer(p_payload ->> 'rid_mastertaskitem', 0) > 0
|
||||||
|
or mti.rid_mastertaskitem = r_mastertaskitemevent.rid_mastertaskitem
|
||||||
|
into r_mastertaskitem;
|
||||||
|
|
||||||
|
select mt.*
|
||||||
|
from core.mastertask mt
|
||||||
|
where
|
||||||
|
mt.rid_mastertask = _try_integer(p_payload ->> 'rid_mastertask', 0)
|
||||||
|
and _try_integer(p_payload ->> 'rid_mastertask', 0) > 0
|
||||||
|
or mt.rid_mastertask = r_mastertaskitem.rid_mastertask
|
||||||
|
into r_mastertask;
|
||||||
|
|
||||||
|
select *
|
||||||
|
from core.taskitem ti
|
||||||
|
where ti.rid_taskitem = _try_integer(p_payload ->> 'rid_taskitem', 0)
|
||||||
|
into r_taskitem;
|
||||||
|
|
||||||
|
if r_taskitem.rid_taskitem > 0
|
||||||
|
then
|
||||||
|
perform set_config('session.rid_taskitem', r_taskitem.rid_taskitem::text, false);
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if nv(r_mastertaskitem.rid_mastertaskitem) = 0
|
||||||
|
then
|
||||||
|
if r_taskitem.rid_taskitem > 0
|
||||||
|
then
|
||||||
|
select *
|
||||||
|
from core.mastertaskitem mti
|
||||||
|
where mti.rid_mastertaskitem = r_taskitem.rid_mastertaskitem
|
||||||
|
into r_mastertaskitem;
|
||||||
|
elsif r_mastertask.rid_mastertask > 0
|
||||||
|
then
|
||||||
|
select *
|
||||||
|
from core.mastertaskitem mti
|
||||||
|
where
|
||||||
|
mti.rid_mastertask = r_mastertask.rid_mastertask
|
||||||
|
and coalesce(mti.inactive, 0) = 0
|
||||||
|
and coalesce(mti.rid_parentmastertaskitem, 0) = 0
|
||||||
|
order by mti.seqno nulls first, mti.rid_mastertaskitem
|
||||||
|
limit 1
|
||||||
|
into r_mastertaskitem;
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if nv(r_mastertaskitemevent.rid_mastertaskitemevent) = 0
|
||||||
|
then
|
||||||
|
if r_mastertaskitem.rid_mastertaskitem > 0
|
||||||
|
then
|
||||||
|
select *
|
||||||
|
from core.mastertaskitemevent mte
|
||||||
|
where
|
||||||
|
mte.rid_mastertaskitem = r_mastertaskitem.rid_mastertaskitem
|
||||||
|
and coalesce(mte.inactive, 0) = 0
|
||||||
|
and mte.status in (1, 2)
|
||||||
|
order by mte.status desc, mte.seqno nulls first, mte.rid_mastertaskitemevent
|
||||||
|
limit 1
|
||||||
|
into r_mastertaskitemevent;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
end if;
|
||||||
|
|
||||||
|
---Check error handler
|
||||||
|
if m_check_error
|
||||||
|
then
|
||||||
|
if nv(r_mastertaskitemevent.status) <> 13
|
||||||
|
then
|
||||||
|
select evt.*
|
||||||
|
from core.mastertaskitemevent evt
|
||||||
|
where
|
||||||
|
evt.rid_mastertaskitem = r_mastertaskitem.rid_mastertaskitem
|
||||||
|
and evt.status = 13
|
||||||
|
order by evt.seqno desc, evt.rid_mastertaskitemevent desc
|
||||||
|
into r_mastertaskitemevent;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
--perform log_event(m_funcname,format('Init check error for rid_mastertaskitem:%s hub:%s ',r_mastertaskitemevent.rid_mastertaskitem,m_rid_hub),bt_enum('eventlog','local notice'));
|
||||||
|
|
||||||
|
if r_mastertaskitemevent.rid_mastertaskitemevent > 0
|
||||||
|
then
|
||||||
|
select r.p_retval, r.p_errmsg, r.p_parms
|
||||||
|
from core.action_init('mastertaskitemevent', r_mastertaskitemevent.rid_mastertaskitemevent
|
||||||
|
, _jsonb_object_cat(p_payload,
|
||||||
|
jsonb_build_object('check_error', true, 'rid_hub', m_rid_hub, 'createmode', 'existing'))
|
||||||
|
) r
|
||||||
|
into m_retval,m_errmsg, p_payload;
|
||||||
|
|
||||||
|
if m_retval > 0
|
||||||
|
then
|
||||||
|
p_retval = m_retval;
|
||||||
|
p_errmsg = m_errmsg;
|
||||||
|
return;
|
||||||
|
end if;
|
||||||
|
else
|
||||||
|
p_retval = 0;
|
||||||
|
p_errmsg = 'Error check enabled but no error event found for mastertaskitem.';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
return;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
---Check cancel handler
|
||||||
|
if m_cancel
|
||||||
|
then
|
||||||
|
if nv(r_mastertaskitemevent.status) <> 6
|
||||||
|
then
|
||||||
|
select evt.*
|
||||||
|
from core.mastertaskitemevent evt
|
||||||
|
where
|
||||||
|
evt.rid_mastertaskitem = r_mastertaskitem.rid_mastertaskitem
|
||||||
|
and evt.status = 6
|
||||||
|
order by evt.seqno desc, evt.rid_mastertaskitemevent desc
|
||||||
|
into r_mastertaskitemevent;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
perform log_event(m_funcname,
|
||||||
|
format('Init cancel for rid_mastertaskitem:%s hub:%s ', r_mastertaskitemevent.rid_mastertaskitem,
|
||||||
|
m_rid_hub), bt_enum('eventlog', 'local notice'));
|
||||||
|
|
||||||
|
if r_mastertaskitemevent.rid_mastertaskitemevent > 0
|
||||||
|
then
|
||||||
|
select r.p_retval, r.p_errmsg, r.p_parms
|
||||||
|
from core.action_init('mastertaskitemevent', r_mastertaskitemevent.rid_mastertaskitemevent
|
||||||
|
, _jsonb_object_cat(p_payload,
|
||||||
|
jsonb_build_object('cancel', true, 'rid_hub', m_rid_hub, 'createmode', 'existing'))
|
||||||
|
) r
|
||||||
|
into m_retval,m_errmsg, p_payload;
|
||||||
|
|
||||||
|
if m_retval > 0
|
||||||
|
then
|
||||||
|
p_retval = m_retval;
|
||||||
|
p_errmsg = m_errmsg;
|
||||||
|
return;
|
||||||
|
end if;
|
||||||
|
else
|
||||||
|
p_retval = 0;
|
||||||
|
p_errmsg = 'cancel called but no cancel event found for mastertaskitem.';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
return;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
|
||||||
|
if nv(m_rid_hub) = 0 and r_taskitem.rid_taskitem > 0 and r_taskitem.rid_hub > 0
|
||||||
|
then
|
||||||
|
m_rid_hub = r_taskitem.rid_hub;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
--perform log_event(m_funcname,format('Init %s(%s) m_rid_hub: %s p_payload: %s',r_taskitem.description,r_taskitem.rid_taskitem,m_rid_hub,p_payload::text),bt_enum('eventlog','local notice'));
|
||||||
|
|
||||||
|
if nv(m_rid_hub) = 0
|
||||||
|
then
|
||||||
|
p_errmsg := 'Missing required parameter: rid_hub';
|
||||||
|
p_retval = 1;
|
||||||
|
return;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if nv(r_mastertaskitemevent.rid_mastertaskitemevent) = 0
|
||||||
|
then
|
||||||
|
p_errmsg := 'Missing required parameter: rid_mastertaskitemevent';
|
||||||
|
p_retval = 1;
|
||||||
|
return;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
perform log_event(m_funcname,
|
||||||
|
format(E'Init rid_mastertaskitemevent %s for rid_mastertaskitem:%s hub:%s \np_payload:%s',
|
||||||
|
r_mastertaskitemevent.rid_mastertaskitemevent, r_mastertaskitem.rid_mastertaskitem,
|
||||||
|
m_rid_hub, p_payload), bt_enum('eventlog', 'local notice'));
|
||||||
|
|
||||||
|
select r.p_retval, r.p_errmsg, r.p_a_rid_taskitem
|
||||||
|
from core.taskitem_get_or_prime(_jsonb_object_cat(p_payload, jsonb_build_object(
|
||||||
|
'parent_rid_taskitem', case
|
||||||
|
when r_taskitem.rid_taskitem = _try_integer(p_payload ->> 'parent_rid_taskitem', 0)
|
||||||
|
then r_taskitem.rid_taskitem_parentaction
|
||||||
|
else _try_integer(p_payload ->> 'parent_rid_taskitem',
|
||||||
|
r_taskitem.rid_taskitem_parentaction)
|
||||||
|
end
|
||||||
|
, 'rid_mastertaskitem', r_mastertaskitem.rid_mastertaskitem
|
||||||
|
, 'rid_mastertaskitemevent', r_mastertaskitemevent.rid_mastertaskitemevent
|
||||||
|
, 'status', r_mastertaskitemevent.status
|
||||||
|
, 'create_mode', p_payload ->> 'create_mode'
|
||||||
|
--,'create_mode','new'
|
||||||
|
))) r
|
||||||
|
into m_retval,m_errmsg,m_temp_rid_list;
|
||||||
|
|
||||||
|
if m_retval > 0
|
||||||
|
then
|
||||||
|
raise 'Error initializing taskitem %',m_errmsg;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
for m_temp_rid in select * from unnest(m_temp_rid_list)
|
||||||
|
loop
|
||||||
|
|
||||||
|
select *
|
||||||
|
from core.taskitem ti
|
||||||
|
where ti.rid_taskitem = m_temp_rid
|
||||||
|
into r_taskitem;
|
||||||
|
|
||||||
|
if nv(r_taskitem.rid_taskitem) = 0
|
||||||
|
then
|
||||||
|
p_retval = 1;
|
||||||
|
p_errmsg = format('Task item not created or found. %s', m_temp_rid);
|
||||||
|
return;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
|
||||||
|
select tie.*
|
||||||
|
from core.taskitemevent tie
|
||||||
|
where
|
||||||
|
tie.rid_mastertaskitemevent = r_mastertaskitemevent.rid_mastertaskitemevent
|
||||||
|
and tie.rid_taskitem = r_taskitem.rid_taskitem
|
||||||
|
into r_taskitemevent;
|
||||||
|
|
||||||
|
--raise exception 'Show me the error: % %',r_taskitemevent.rid_taskitemevent, r_taskitemevent.rid_taskitem;
|
||||||
|
--raise notice 'New/Update r_taskitemevent? %',r_taskitemevent;
|
||||||
|
-- if r_taskitem.rid_taskitem > 0
|
||||||
|
-- and p_payload->'jsonvalue'->>'errorcode' is not null
|
||||||
|
-- then
|
||||||
|
-- update core.taskitem u
|
||||||
|
-- set jsonvalue = _jsonb_object_cat(u.jsonvalue, jsonb_build_object(
|
||||||
|
-- 'errorcode', _bv(p_payload->'jsonvalue'->>'errorcode',u.jsonvalue->>'errorcode')
|
||||||
|
-- ,'errormessage',_bv( p_payload->'jsonvalue'->>'errormessage',u.jsonvalue->>'errormessage')
|
||||||
|
-- ,'alerttype', _bv(p_payload->'jsonvalue'->>'alerttype',u.jsonvalue->>'alerttype')
|
||||||
|
-- ))
|
||||||
|
-- where u.rid_taskitem = r_taskitem.rid_taskitem;
|
||||||
|
--
|
||||||
|
-- end if;
|
||||||
|
|
||||||
|
if r_taskitemevent.rid_taskitemevent > 0
|
||||||
|
then
|
||||||
|
|
||||||
|
|
||||||
|
update core.taskitemevent u
|
||||||
|
set
|
||||||
|
retval = 0
|
||||||
|
, errmsg = null
|
||||||
|
, jsonvalue = _jsonb_object_cat(u.jsonvalue, (
|
||||||
|
select mte.jsonvalue
|
||||||
|
from core.mastertaskitemevent mte
|
||||||
|
where mte.rid_mastertaskitemevent = u.rid_mastertaskitemevent
|
||||||
|
limit 1
|
||||||
|
), p_payload -> 'jsonvalue')
|
||||||
|
where u.rid_taskitemevent = r_taskitemevent.rid_taskitemevent;
|
||||||
|
|
||||||
|
select r.p_retval, r.p_errmsg
|
||||||
|
from core.event_created(r_taskitemevent.rid_taskitemevent
|
||||||
|
, jsonb_build_object('operation', 'UPDATE')) r
|
||||||
|
into m_retval,m_errmsg;
|
||||||
|
|
||||||
|
if m_retval > 0
|
||||||
|
then
|
||||||
|
raise '%',m_errmsg;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
select e.*
|
||||||
|
from core.taskitemevent e
|
||||||
|
where e.rid_taskitemevent = r_taskitemevent.rid_taskitemevent
|
||||||
|
into r_taskitemevent;
|
||||||
|
|
||||||
|
if length(r_taskitemevent.errmsg) > 0
|
||||||
|
then
|
||||||
|
p_retval = 1;
|
||||||
|
p_errmsg = r_taskitemevent.errmsg;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
|
||||||
|
insert
|
||||||
|
into core.taskitemevent( createddatetime, description, rid_mastertaskitemevent, rid_taskitem, status
|
||||||
|
--, duedatetime
|
||||||
|
--, escalated
|
||||||
|
, jsonvalue, outcome, rid_hub_user, retval)
|
||||||
|
select now()
|
||||||
|
, r_mastertaskitemevent.description
|
||||||
|
, r_mastertaskitemevent.rid_mastertaskitemevent
|
||||||
|
, r_taskitem.rid_taskitem
|
||||||
|
, _bv(r_mastertaskitemevent.status, m_status)
|
||||||
|
, _jsonb_object_cat((
|
||||||
|
select mte.jsonvalue
|
||||||
|
from core.mastertaskitemevent mte
|
||||||
|
where mte.rid_mastertaskitemevent = r_mastertaskitemevent.rid_mastertaskitemevent
|
||||||
|
limit 1
|
||||||
|
), p_payload -> 'jsonvalue')
|
||||||
|
, m_outcome
|
||||||
|
, m_rid_hub_user
|
||||||
|
, 0
|
||||||
|
where
|
||||||
|
not exists (
|
||||||
|
select 1
|
||||||
|
from core.taskitemevent tie
|
||||||
|
where
|
||||||
|
tie.rid_mastertaskitemevent = r_mastertaskitemevent.rid_mastertaskitemevent
|
||||||
|
and tie.rid_taskitem = r_taskitem.rid_taskitem
|
||||||
|
)
|
||||||
|
returning taskitemevent.*
|
||||||
|
into r_taskitemevent;
|
||||||
|
|
||||||
|
-- select r.p_retval,r.p_errmsg
|
||||||
|
-- from core.event_created(r_taskitemevent.rid_taskitemevent
|
||||||
|
-- ,jsonb_build_object('operation','UPDATE')) r
|
||||||
|
-- into m_retval,m_errmsg;
|
||||||
|
--
|
||||||
|
-- if m_retval > 0
|
||||||
|
-- then
|
||||||
|
-- raise '%',m_errmsg;
|
||||||
|
-- end if;
|
||||||
|
|
||||||
|
|
||||||
|
select e.*
|
||||||
|
from core.taskitemevent e
|
||||||
|
where e.rid_taskitemevent = r_taskitemevent.rid_taskitemevent
|
||||||
|
into r_taskitemevent;
|
||||||
|
|
||||||
|
if length(r_taskitemevent.errmsg) > 1
|
||||||
|
then
|
||||||
|
p_retval = 1;
|
||||||
|
p_errmsg = r_taskitemevent.errmsg;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
end if;
|
||||||
|
|
||||||
|
with
|
||||||
|
recursive
|
||||||
|
tasklist_events as (
|
||||||
|
select ti.rid_taskitem
|
||||||
|
, 1 as level
|
||||||
|
, coalesce(ti.seqno, 0) as seqno
|
||||||
|
, ti.status
|
||||||
|
from core.taskitem ti
|
||||||
|
inner join core.mastertaskitem mti on mti.rid_mastertaskitem = ti.rid_mastertaskitem
|
||||||
|
where
|
||||||
|
(ti.rid_tasklist = r_taskitem.rid_tasklist
|
||||||
|
or ti.rid_tasklist in (
|
||||||
|
select tl.rid_tasklist_parent
|
||||||
|
from core.tasklistlink tl
|
||||||
|
where tl.rid_tasklist_child = r_taskitem.rid_tasklist
|
||||||
|
union
|
||||||
|
select tl.rid_tasklist_child
|
||||||
|
from core.tasklistlink tl
|
||||||
|
where tl.rid_tasklist_parent = r_taskitem.rid_tasklist
|
||||||
|
)
|
||||||
|
)
|
||||||
|
-- and mti.rid_mastertaskitem in (
|
||||||
|
-- select ev2.rid_mastertaskitem
|
||||||
|
-- from core.mastertaskitemeventreaction er
|
||||||
|
-- inner join core.mastertaskitemevent ev2 on ev2.rid_mastertaskitemevent = er.rid_mastertaskitemevent_target
|
||||||
|
-- where er.rid_mastertaskitemevent = r_taskitemevent.rid_mastertaskitemevent
|
||||||
|
-- )
|
||||||
|
union
|
||||||
|
select ti.rid_taskitem
|
||||||
|
, te.level + 1
|
||||||
|
, coalesce(ti.seqno, 0) as seqno
|
||||||
|
, ti.status
|
||||||
|
from core.taskitem ti
|
||||||
|
inner join tasklist_events te on te.rid_taskitem = ti.rid_parenttaskitem
|
||||||
|
|
||||||
|
)
|
||||||
|
select e.rid_taskitem
|
||||||
|
from tasklist_events e
|
||||||
|
where nv(e.status) in (2, 10)
|
||||||
|
order by e.level desc, e.status desc, e.seqno desc
|
||||||
|
limit 1
|
||||||
|
into m_final_rid_taskitem;
|
||||||
|
|
||||||
|
j_obj = _jsonb_object_cat(to_jsonb(r_taskitemevent), jsonb_build_object(
|
||||||
|
'jsonvalue', _jsonb_object_cat(r_taskitemevent.jsonvalue, jsonb_build_object(
|
||||||
|
'rid_tasklist', r_taskitem.rid_tasklist,
|
||||||
|
'rid_tasklist_event', (
|
||||||
|
select jsonb_agg(distinct ti.rid_tasklist)
|
||||||
|
from core.mastertaskitemeventreaction er
|
||||||
|
inner join core.mastertaskitemevent mtiet
|
||||||
|
on mtiet.rid_mastertaskitemevent = er.rid_mastertaskitemevent_target
|
||||||
|
inner join core.taskitem ti on ti.rid_mastertaskitem = mtiet.rid_mastertaskitem
|
||||||
|
and ti.rid_hub = r_taskitem.rid_hub
|
||||||
|
and ti.rid_tasklist = r_taskitem.rid_tasklist
|
||||||
|
where er.rid_mastertaskitemevent = r_taskitemevent.rid_mastertaskitemevent
|
||||||
|
)
|
||||||
|
, 'rid_tasklist_next', (
|
||||||
|
select jsonb_agg(distinct tl.rid_tasklist)
|
||||||
|
from core.taskitem ti
|
||||||
|
inner join core.mastertaskitem mti on mti.rid_mastertaskitem = ti.rid_mastertaskitem
|
||||||
|
inner join core.mastertask mtj on mti.guid = mti.jsonvalue ->> 'guid_mastertask_next'
|
||||||
|
inner join core.tasklist tl on tl.rid_mastertask = mtj.rid_mastertask
|
||||||
|
and tl.rid_hub = r_taskitem.rid_hub
|
||||||
|
where ti.rid_taskitem = r_taskitemevent.rid_taskitem
|
||||||
|
)
|
||||||
|
, 'select_taskitem_guid', (
|
||||||
|
select ti.guid
|
||||||
|
from core.taskitem ti
|
||||||
|
where
|
||||||
|
(m_final_rid_taskitem > 0
|
||||||
|
and ti.rid_taskitem = m_final_rid_taskitem
|
||||||
|
or nv(m_final_rid_taskitem) = 0
|
||||||
|
and ti.rid_tasklist = r_taskitem.rid_tasklist
|
||||||
|
)
|
||||||
|
order by
|
||||||
|
case
|
||||||
|
when ti.status in (10) then 2
|
||||||
|
when ti.status in (0, 1, 2) then 1
|
||||||
|
else 99
|
||||||
|
end
|
||||||
|
, ti.status desc, ti.seqno desc
|
||||||
|
limit 1
|
||||||
|
)
|
||||||
|
, 'openactions', (
|
||||||
|
select count(ti.rid_taskitem) filter ( where
|
||||||
|
coalesce(ti.status, 0) in (
|
||||||
|
core._enumi('eventstatus', 'todo'), core._enumi('eventstatus', 'planned')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
from core.taskitem ti
|
||||||
|
where
|
||||||
|
ti.rid_tasklist in (
|
||||||
|
select r_taskitem.rid_tasklist
|
||||||
|
union
|
||||||
|
select tl.rid_tasklist_child
|
||||||
|
from core.tasklistlink tl
|
||||||
|
where tl.rid_tasklist_parent = r_taskitem.rid_tasklist
|
||||||
|
union
|
||||||
|
select tl.rid_tasklist_parent
|
||||||
|
from core.tasklistlink tl
|
||||||
|
where tl.rid_tasklist_child = r_taskitem.rid_tasklist
|
||||||
|
)
|
||||||
|
and nv(ti.subitem) = 0
|
||||||
|
and coalesce(ti.status, 0) <> coalesce(core._enumi('eventstatus', 'Canceled'), 0)
|
||||||
|
)
|
||||||
|
))
|
||||||
|
, 'WFL', to_jsonb(r_taskitem)));
|
||||||
|
|
||||||
|
j_obj = _jsonb_object_cat(j_obj
|
||||||
|
, jsonb_build_object('jsonvalue'
|
||||||
|
, _jsonb_object_cat(
|
||||||
|
j_obj -> 'jsonvalue'
|
||||||
|
, jsonb_build_object('complete'
|
||||||
|
, (j_obj -> 'jsonvalue' ->> 'rid_tasklist_event' is null
|
||||||
|
and j_obj -> 'jsonvalue' ->> 'rid_tasklist_next' is null
|
||||||
|
and _try_integer(j_obj -> 'jsonvalue' ->> 'openactions', 0) = 0
|
||||||
|
and nv(r_taskitemevent.jsonvalue ->> 'errorcode') not ilike 'E%'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
j_result = j_result || j_obj;
|
||||||
|
|
||||||
|
if m_final_rid_taskitem > 0 and m_final_rid_taskitem is distinct from r_taskitem.rid_taskitem
|
||||||
|
then
|
||||||
|
j_obj = _jsonb_object_cat(to_jsonb(ev), jsonb_build_object(
|
||||||
|
'jsonvalue', jsonb_build_object(
|
||||||
|
'rid_tasklist', ti.rid_tasklist
|
||||||
|
, 'select_taskitem_guid', (
|
||||||
|
select ti.guid
|
||||||
|
from core.taskitem ti
|
||||||
|
where
|
||||||
|
(m_final_rid_taskitem > 0
|
||||||
|
and ti.rid_taskitem = m_final_rid_taskitem
|
||||||
|
or nv(m_final_rid_taskitem) = 0
|
||||||
|
and ti.rid_tasklist = r_taskitem.rid_tasklist
|
||||||
|
)
|
||||||
|
order by case when ti.status in (0, 1, 2) then 1 else 99 end, ti.status desc, ti.seqno desc
|
||||||
|
limit 1
|
||||||
|
)
|
||||||
|
, 'openactions', (
|
||||||
|
select count(ti.rid_taskitem) filter ( where
|
||||||
|
coalesce(ti.status, 0) in (
|
||||||
|
core._enumi('eventstatus', 'todo'), core._enumi('eventstatus', 'planned')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
from core.taskitem ti
|
||||||
|
where
|
||||||
|
ti.rid_tasklist = r_taskitem.rid_tasklist
|
||||||
|
and nv(ti.subitem) = 0
|
||||||
|
and coalesce(ti.status, 0) <> coalesce(core._enumi('eventstatus', 'Canceled'), 0)
|
||||||
|
limit 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
, 'WFL', to_jsonb(ti)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
from core.taskitem ti
|
||||||
|
inner join lateral (
|
||||||
|
select *
|
||||||
|
from core.taskitemevent ev
|
||||||
|
where
|
||||||
|
ev.rid_taskitem = ti.rid_taskitem
|
||||||
|
and ev.status in (2, 4)
|
||||||
|
order by ev.status desc
|
||||||
|
limit 1
|
||||||
|
) ev on ev.rid_taskitem = ti.rid_taskitem
|
||||||
|
where ti.rid_taskitem = m_final_rid_taskitem;
|
||||||
|
|
||||||
|
j_result = j_result || j_obj;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
|
||||||
|
end loop;
|
||||||
|
|
||||||
|
--perform log_event(m_funcname,format('Result j_result: %s',j_result),bt_enum('eventlog','local notice'));
|
||||||
|
|
||||||
|
p_payload = j_result;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN others THEN
|
||||||
|
GET STACKED DIAGNOSTICS
|
||||||
|
m_errmsg = MESSAGE_TEXT
|
||||||
|
,m_errcontext = PG_EXCEPTION_CONTEXT
|
||||||
|
,m_errdetail = PG_EXCEPTION_DETAIL
|
||||||
|
,m_errhint = PG_EXCEPTION_HINT
|
||||||
|
,m_errstate = RETURNED_SQLSTATE;
|
||||||
|
|
||||||
|
p_errmsg := get_err_msg(m_funcname, m_errmsg, m_errcontext, m_errdetail, m_errhint, m_errstate);
|
||||||
|
p_retval = 1;
|
||||||
|
perform log_event(m_funcname, format('Benchmark Err %s Items: %s', array_length(m_temp_rid_list, 1),
|
||||||
|
(clock_timestamp() - m_tm)::interval::text),
|
||||||
|
bt_enum('eventlog', 'local notice'));
|
||||||
|
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
select * from core.ui_action_event(jsonb_build_object('check_error',true
|
||||||
|
,'rid_mastertaskitem',(
|
||||||
|
|
||||||
|
select mti.rid_mastertaskitem from core.mastertaskitem mti
|
||||||
|
where mti.guid = '7a7348ec-7db6-4f13-bca7-c7840de1d22c'
|
||||||
|
),'rid_hub',20405))
|
||||||
|
;
|
||||||
|
|
||||||
|
select * from core.ui_action_event(jsonb_build_object('check_error',true
|
||||||
|
,'rid_mastertaskitem',(
|
||||||
|
|
||||||
|
select mti.rid_mastertaskitem from core.mastertaskitem mti
|
||||||
|
where mti.guid = '7a7348ec-7db6-4f13-bca7-c7840de1d22c'
|
||||||
|
),'rid_hub',20405))
|
||||||
|
;
|
||||||
|
|
||||||
|
select * from v_eventlog
|
||||||
|
|
||||||
|
selecT * from core.taskitem ti
|
||||||
|
order by ti.rid_taskitem desc
|
||||||
|
|
||||||
|
|
||||||
|
select mti.rid_mastertask,mti.rid_mastertaskitem, mti.description, mti.guid from core.mastertaskitem mti order by mti.rid_mastertaskitem desc;
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
--select cli.rid_hub from t_adproclient cli where cli.clientid = '92092101980891'
|
||||||
Vendored
+8
-3
@@ -138,7 +138,9 @@ BEGIN
|
|||||||
|
|
||||||
select ctl.rid_parent as rid_parent
|
select ctl.rid_parent as rid_parent
|
||||||
, ctl.tableprefix as parent_prefix
|
, ctl.tableprefix as parent_prefix
|
||||||
from tasklist ctl --inner join t_adproclient cli on cli.rid_adproclient = ctl.rid_parent -- and ctl.tableprefix = 'tcli'
|
from tasklist ctl
|
||||||
|
--inner join t_adproclient cli on cli.rid_adproclient = ctl.rid_parent
|
||||||
|
-- and ctl.tableprefix = 'tcli'
|
||||||
where ctl.rid_tasklist = p_data_rid into m_data_rid, m_data_prefix;
|
where ctl.rid_tasklist = p_data_rid into m_data_rid, m_data_prefix;
|
||||||
|
|
||||||
elsif p_data_prefix::citext = 'ehr'
|
elsif p_data_prefix::citext = 'ehr'
|
||||||
@@ -545,7 +547,8 @@ BEGIN
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
---Use this code to test
|
---Use this code to test
|
||||||
select pl_writefile('/mnt/t/temp/t.docx',r.p_doc) ,r.*
|
select pl_writefile('/mnt/t/temp/t.docx',r.p_doc)
|
||||||
|
,r.*
|
||||||
from mm_proc('docx','allfieldvalues', (
|
from mm_proc('docx','allfieldvalues', (
|
||||||
select tat.maindoc
|
select tat.maindoc
|
||||||
from templateattachment tat
|
from templateattachment tat
|
||||||
@@ -1330,7 +1333,9 @@ BEGIN
|
|||||||
inner
|
inner
|
||||||
join tmp_merge_init_fields d
|
join tmp_merge_init_fields d
|
||||||
on d . mergetag = c . mergetag and d . tblparent = r_lp_t . tblid
|
on d . mergetag = c . mergetag and d . tblparent = r_lp_t . tblid
|
||||||
where f . grand_rid = r_lp_t . parent_rid and f . merge_type = G_MTYPE_TBLROOT --and f.parent_rid = any(a_tblroot) --and d.table_level > 0
|
where f . grand_rid = r_lp_t . parent_rid and f . merge_type = G_MTYPE_TBLROOT
|
||||||
|
--and f.parent_rid = any(a_tblroot)
|
||||||
|
--and d.table_level > 0
|
||||||
|
|
||||||
group by f . rid ) loop
|
group by f . rid ) loop
|
||||||
raise notice 'Inner Loop: %', r_lp_c . qry;
|
raise notice 'Inner Loop: %', r_lp_c . qry;
|
||||||
|
|||||||
Reference in New Issue
Block a user