Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60602d1de7 | ||
|
|
8d19258aa0 | ||
|
|
5cba6beeb1 | ||
|
|
a0838e4bdc | ||
|
|
44fb77efd6 | ||
|
|
4fab2fe652 |
@@ -0,0 +1,42 @@
|
|||||||
|
package com.pgtidy.datagrip
|
||||||
|
|
||||||
|
import com.intellij.openapi.actionSystem.AnAction
|
||||||
|
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||||
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
|
import com.intellij.openapi.progress.ProgressIndicator
|
||||||
|
import com.intellij.openapi.progress.Task
|
||||||
|
import com.intellij.openapi.ui.Messages
|
||||||
|
|
||||||
|
class PgTidyShowVersionAction : AnAction() {
|
||||||
|
|
||||||
|
override fun actionPerformed(e: AnActionEvent) {
|
||||||
|
val project = e.project
|
||||||
|
|
||||||
|
object : Task.Backgroundable(project, "PgTidy: checking version…", false) {
|
||||||
|
override fun run(indicator: ProgressIndicator) {
|
||||||
|
val proc = try {
|
||||||
|
ProcessBuilder("pgtidy", "version")
|
||||||
|
.redirectErrorStream(false)
|
||||||
|
.start()
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
ApplicationManager.getApplication().invokeLater {
|
||||||
|
Messages.showErrorDialog(project, "Cannot start pgtidy: ${ex.message}", "PgTidy")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val output = proc.inputStream.bufferedReader().readText()
|
||||||
|
val stderr = proc.errorStream.bufferedReader().readText()
|
||||||
|
val exit = proc.waitFor()
|
||||||
|
|
||||||
|
ApplicationManager.getApplication().invokeLater {
|
||||||
|
if (exit != 0) {
|
||||||
|
Messages.showErrorDialog(project, stderr.ifBlank { "pgtidy exited with code $exit" }, "PgTidy")
|
||||||
|
return@invokeLater
|
||||||
|
}
|
||||||
|
Messages.showInfoMessage(project, output.trim(), "PgTidy Version")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.queue()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package com.pgtidy.datagrip
|
||||||
|
|
||||||
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.wm.StatusBar
|
||||||
|
import com.intellij.openapi.wm.StatusBarWidget
|
||||||
|
import com.intellij.openapi.wm.StatusBarWidgetFactory
|
||||||
|
import com.intellij.util.Consumer
|
||||||
|
import java.awt.event.MouseEvent
|
||||||
|
|
||||||
|
private const val WIDGET_ID = "com.pgtidy.StatusBarWidget"
|
||||||
|
|
||||||
|
class PgTidyStatusBarWidget(private val project: Project) : StatusBarWidget, StatusBarWidget.TextPresentation {
|
||||||
|
|
||||||
|
private var statusBar: StatusBar? = null
|
||||||
|
private var text: String = "pgtidy: …"
|
||||||
|
|
||||||
|
override fun ID(): String = WIDGET_ID
|
||||||
|
|
||||||
|
override fun install(statusBar: StatusBar) {
|
||||||
|
this.statusBar = statusBar
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
statusBar = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPresentation(): StatusBarWidget.WidgetPresentation = this
|
||||||
|
|
||||||
|
override fun getText(): String = text
|
||||||
|
|
||||||
|
override fun getAlignment(): Float = java.awt.Component.CENTER_ALIGNMENT
|
||||||
|
|
||||||
|
override fun getTooltipText(): String = "PgTidy version — click to refresh"
|
||||||
|
|
||||||
|
override fun getClickConsumer(): Consumer<MouseEvent> = Consumer { refresh() }
|
||||||
|
|
||||||
|
fun refresh() {
|
||||||
|
ApplicationManager.getApplication().executeOnPooledThread {
|
||||||
|
text = try {
|
||||||
|
val proc = ProcessBuilder("pgtidy", "version")
|
||||||
|
.redirectErrorStream(true)
|
||||||
|
.start()
|
||||||
|
val output = proc.inputStream.bufferedReader().readText().trim()
|
||||||
|
val exit = proc.waitFor()
|
||||||
|
if (exit == 0 && output.isNotBlank()) output else "pgtidy: not found"
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
"pgtidy: not found"
|
||||||
|
}
|
||||||
|
ApplicationManager.getApplication().invokeLater {
|
||||||
|
statusBar?.updateWidget(WIDGET_ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PgTidyStatusBarWidgetFactory : StatusBarWidgetFactory {
|
||||||
|
override fun getId(): String = WIDGET_ID
|
||||||
|
|
||||||
|
override fun getDisplayName(): String = "PgTidy Version"
|
||||||
|
|
||||||
|
override fun isAvailable(project: Project): Boolean = true
|
||||||
|
|
||||||
|
override fun createWidget(project: Project): StatusBarWidget = PgTidyStatusBarWidget(project)
|
||||||
|
|
||||||
|
override fun disposeWidget(widget: StatusBarWidget) {
|
||||||
|
widget.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun canBeEnabledOn(statusBar: StatusBar): Boolean = true
|
||||||
|
}
|
||||||
@@ -21,8 +21,20 @@
|
|||||||
<add-to-group group-id="EditorPopupMenu" anchor="first"/>
|
<add-to-group group-id="EditorPopupMenu" anchor="first"/>
|
||||||
<keyboard-shortcut keymap="$default" first-keystroke="ctrl alt shift P"/>
|
<keyboard-shortcut keymap="$default" first-keystroke="ctrl alt shift P"/>
|
||||||
</action>
|
</action>
|
||||||
|
<action id="com.pgtidy.ShowVersion"
|
||||||
|
class="com.pgtidy.datagrip.PgTidyShowVersionAction"
|
||||||
|
text="Show PgTidy Version"
|
||||||
|
description="Show the installed pgtidy binary version">
|
||||||
|
<add-to-group group-id="ToolsMenu" anchor="last"/>
|
||||||
|
</action>
|
||||||
</actions>
|
</actions>
|
||||||
|
|
||||||
|
<extensions defaultExtensionNs="com.intellij">
|
||||||
|
<statusBarWidgetFactory id="com.pgtidy.StatusBarWidget"
|
||||||
|
implementation="com.pgtidy.datagrip.PgTidyStatusBarWidgetFactory"
|
||||||
|
order="last"/>
|
||||||
|
</extensions>
|
||||||
|
|
||||||
<extensions defaultExtensionNs="com.redhat.devtools.lsp4ij">
|
<extensions defaultExtensionNs="com.redhat.devtools.lsp4ij">
|
||||||
<server id="com.pgtidy.lsp"
|
<server id="com.pgtidy.lsp"
|
||||||
name="PgTidy"
|
name="PgTidy"
|
||||||
|
|||||||
+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.3
|
pkgver=0.0.5
|
||||||
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.3
|
Version: 0.0.5
|
||||||
Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
Summary: PostgreSQL SQL formatter and linter
|
Summary: PostgreSQL SQL formatter and linter
|
||||||
|
|
||||||
|
|||||||
+153
-8
@@ -379,6 +379,7 @@ func formatBodyStatements(text string, st config.Style) string {
|
|||||||
stmt []bline
|
stmt []bline
|
||||||
parenDepth int
|
parenDepth int
|
||||||
blockDepth int // 0=col-0 (BEGIN/END/EXCEPTION), 1=body, 2=nested…
|
blockDepth int // 0=col-0 (BEGIN/END/EXCEPTION), 1=body, 2=nested…
|
||||||
|
caseDepth int // depth of open CASE…END expressions (WHEN…THEN is not a block opener)
|
||||||
inException bool
|
inException bool
|
||||||
pendingBlanks int
|
pendingBlanks int
|
||||||
depthInc bool // increment blockDepth after next flush
|
depthInc bool // increment blockDepth after next flush
|
||||||
@@ -440,13 +441,9 @@ func formatBodyStatements(text string, st config.Style) string {
|
|||||||
stmtLines = joinThenToCondition(stmt)
|
stmtLines = joinThenToCondition(stmt)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, ll := range stmtLines {
|
formattedLines := formatBodyStmtLines(stmtLines, baseIndent, st)
|
||||||
if i == 0 || ll.indent == "" {
|
for _, line := range formattedLines {
|
||||||
result.WriteString(baseIndent)
|
result.WriteString(line)
|
||||||
} else {
|
|
||||||
result.WriteString(ll.indent)
|
|
||||||
}
|
|
||||||
result.WriteString(ll.text)
|
|
||||||
result.WriteString(nl)
|
result.WriteString(nl)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -529,18 +526,41 @@ func formatBodyStatements(text string, st config.Style) string {
|
|||||||
}
|
}
|
||||||
if parenDepth == 0 && tok.Kind == lexer.Ident {
|
if parenDepth == 0 && tok.Kind == lexer.Ident {
|
||||||
lastD0Kw = lowerASCII(tok.Text)
|
lastD0Kw = lowerASCII(tok.Text)
|
||||||
|
switch lastD0Kw {
|
||||||
|
case "case":
|
||||||
|
caseDepth++
|
||||||
|
case "end":
|
||||||
|
if caseDepth > 0 {
|
||||||
|
caseDepth--
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if parenDepth == 0 && len(stmt) > 0 {
|
if parenDepth == 0 && len(stmt) > 0 {
|
||||||
switch lastD0Kw {
|
switch lastD0Kw {
|
||||||
case "then", "loop", "begin":
|
case "then":
|
||||||
|
// A THEN ending a CASE…WHEN branch is not a PL/pgSQL block
|
||||||
|
// opener; only one matching END closes the whole CASE, so
|
||||||
|
// treating each WHEN…THEN as a block open would permanently
|
||||||
|
// inflate blockDepth.
|
||||||
|
if caseDepth == 0 {
|
||||||
|
fw0 := lowerASCII(firstBodyKeyword(stmt[0].text))
|
||||||
|
if fw0 != "elsif" && fw0 != "elseif" {
|
||||||
|
depthInc = true
|
||||||
|
}
|
||||||
|
flush()
|
||||||
|
}
|
||||||
|
case "loop", "begin":
|
||||||
fw0 := lowerASCII(firstBodyKeyword(stmt[0].text))
|
fw0 := lowerASCII(firstBodyKeyword(stmt[0].text))
|
||||||
if fw0 != "elsif" && fw0 != "elseif" {
|
if fw0 != "elsif" && fw0 != "elseif" {
|
||||||
depthInc = true
|
depthInc = true
|
||||||
}
|
}
|
||||||
flush()
|
flush()
|
||||||
case "else", "exception":
|
case "else", "exception":
|
||||||
|
if caseDepth > 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
flush()
|
flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -550,6 +570,131 @@ func formatBodyStatements(text string, st config.Style) string {
|
|||||||
return result.String()
|
return result.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// formatBodyStmtLines formats one flushed PL/pgSQL statement at its contextual
|
||||||
|
// base indent. Multi-line UPDATE/DELETE statements inside PL/pgSQL get their
|
||||||
|
// top-level SET/WHERE/AND/OR clauses realigned under the statement while nested
|
||||||
|
// subqueries keep their original indentation. Non-DML statements keep
|
||||||
|
// continuation indentation, except that standalone structural keywords such as
|
||||||
|
// THEN are aligned with the block opener.
|
||||||
|
func formatBodyStmtLines(lines []bline, baseIndent string, st config.Style) []string {
|
||||||
|
if len(lines) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if looksLikeMultiLineBodyDML(lines) {
|
||||||
|
return reindentBodyDML(lines, baseIndent, st)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make([]string, 0, len(lines))
|
||||||
|
for i, ll := range lines {
|
||||||
|
text := ll.text
|
||||||
|
indent := baseIndent
|
||||||
|
if i > 0 && ll.indent != "" && !isStandaloneBodyKeyword(ll.text, "then", "else", "elsif", "elseif") {
|
||||||
|
indent = ll.indent
|
||||||
|
}
|
||||||
|
out = append(out, indent+text)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func looksLikeMultiLineBodyDML(lines []bline) bool {
|
||||||
|
if len(lines) < 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
kw := lowerASCII(firstBodyKeyword(lines[0].text))
|
||||||
|
return kw == "update" || kw == "delete"
|
||||||
|
}
|
||||||
|
|
||||||
|
func reindentBodyDML(lines []bline, baseIndent string, st config.Style) []string {
|
||||||
|
out := make([]string, 0, len(lines)+1)
|
||||||
|
afterWhere := false
|
||||||
|
parenDepth := 0
|
||||||
|
for i, ll := range lines {
|
||||||
|
text := strings.TrimRight(ll.text, " ")
|
||||||
|
lineDepth := parenDepth
|
||||||
|
kw := lowerASCII(firstBodyKeyword(text))
|
||||||
|
if afterWhere && lineDepth == 0 && kw != "and" && kw != "or" {
|
||||||
|
out = append(out, baseIndent+st.Indent+st.Indent+strings.TrimSpace(text))
|
||||||
|
afterWhere = false
|
||||||
|
updateBodyParenDepth(text, &parenDepth)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if lineDepth == 0 && (kw == "set" || kw == "where" || kw == "values" || kw == "returning") {
|
||||||
|
if kw == "where" {
|
||||||
|
whereText := strings.TrimSpace(text)
|
||||||
|
fields := strings.Fields(whereText)
|
||||||
|
nextKw := ""
|
||||||
|
if i+1 < len(lines) {
|
||||||
|
nextKw = lowerASCII(firstBodyKeyword(lines[i+1].text))
|
||||||
|
}
|
||||||
|
if len(fields) > 1 && (nextKw == "and" || nextKw == "or") {
|
||||||
|
out = append(out, baseIndent+fields[0])
|
||||||
|
out = append(out, baseIndent+st.Indent+st.Indent+strings.TrimSpace(whereText[len(fields[0]):]))
|
||||||
|
afterWhere = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
afterWhere = len(fields) == 1
|
||||||
|
}
|
||||||
|
out = append(out, baseIndent+strings.TrimSpace(text))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if lineDepth == 0 && (kw == "and" || kw == "or") {
|
||||||
|
out = append(out, baseIndent+st.Indent+strings.TrimSpace(text))
|
||||||
|
afterWhere = false
|
||||||
|
updateBodyParenDepth(text, &parenDepth)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
out = append(out, baseIndent+strings.TrimSpace(text))
|
||||||
|
} else if ll.indent != "" {
|
||||||
|
out = append(out, ll.indent+strings.TrimSpace(text))
|
||||||
|
} else {
|
||||||
|
out = append(out, baseIndent+strings.TrimSpace(text))
|
||||||
|
}
|
||||||
|
afterWhere = false
|
||||||
|
updateBodyParenDepth(text, &parenDepth)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateBodyParenDepth(s string, depth *int) {
|
||||||
|
for _, tok := range lexer.Lex(s) {
|
||||||
|
switch tok.Kind {
|
||||||
|
case lexer.LParen, lexer.LBracket:
|
||||||
|
(*depth)++
|
||||||
|
case lexer.RParen, lexer.RBracket:
|
||||||
|
if *depth > 0 {
|
||||||
|
(*depth)--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func significantBodyTokens(s string) []cst.Tok {
|
||||||
|
var toks []cst.Tok
|
||||||
|
for _, tok := range lexer.Lex(s) {
|
||||||
|
if tok.IsTrivia() || tok.Kind == lexer.EOF {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
toks = append(toks, cst.Tok{Tok: tok})
|
||||||
|
}
|
||||||
|
return toks
|
||||||
|
}
|
||||||
|
|
||||||
|
func isStandaloneBodyKeyword(s string, kws ...string) bool {
|
||||||
|
toks := significantBodyTokens(s)
|
||||||
|
if len(toks) != 1 || toks[0].Tok.Kind != lexer.Ident {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
low := lowerASCII(toks[0].Tok.Text)
|
||||||
|
for _, kw := range kws {
|
||||||
|
if low == kw {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// joinThenToCondition merges a THEN line (on its own bline) into the preceding
|
// joinThenToCondition merges a THEN line (on its own bline) into the preceding
|
||||||
// condition line when plpgsql_if_then_newline is false.
|
// condition line when plpgsql_if_then_newline is false.
|
||||||
func joinThenToCondition(lines []bline) []bline {
|
func joinThenToCondition(lines []bline) []bline {
|
||||||
|
|||||||
@@ -107,6 +107,66 @@ func TestFormatMmProcBroken(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFormatIssue1PLpgSQLIndenting(t *testing.T) {
|
||||||
|
src := "CREATE FUNCTION f() RETURNS void LANGUAGE plpgsql AS $$\n" +
|
||||||
|
"DECLARE\n" +
|
||||||
|
" r_lp record;\n" +
|
||||||
|
"BEGIN\n" +
|
||||||
|
" if r_lp.total > 0\n" +
|
||||||
|
" and r_lp.totaldone >= r_lp.total\n" +
|
||||||
|
" then\n" +
|
||||||
|
" update core.process u\n" +
|
||||||
|
" set status = 'done'\n" +
|
||||||
|
" where u.rid_process = r_lp.rid_process\n" +
|
||||||
|
" and nv(u.status) <> 'done';\n" +
|
||||||
|
" elsif r_lp.total > 0\n" +
|
||||||
|
" then\n" +
|
||||||
|
" update core.process u\n" +
|
||||||
|
" set status = 'open'\n" +
|
||||||
|
" where u.rid_process = r_lp.rid_process\n" +
|
||||||
|
" and nv(u.status) <> 'open';\n" +
|
||||||
|
"\n" +
|
||||||
|
" end if;\n" +
|
||||||
|
"$$;\n"
|
||||||
|
|
||||||
|
want := "CREATE FUNCTION f(\n" +
|
||||||
|
")\n" +
|
||||||
|
"RETURNS void\n" +
|
||||||
|
"LANGUAGE plpgsql\n" +
|
||||||
|
"AS\n" +
|
||||||
|
"$$\n" +
|
||||||
|
"DECLARE\n" +
|
||||||
|
" r_lp record;\n" +
|
||||||
|
"BEGIN\n" +
|
||||||
|
" if r_lp.total > 0\n" +
|
||||||
|
" and r_lp.totaldone >= r_lp.total\n" +
|
||||||
|
" then\n" +
|
||||||
|
" update core.process u\n" +
|
||||||
|
" set status = 'done'\n" +
|
||||||
|
" where\n" +
|
||||||
|
" u.rid_process = r_lp.rid_process\n" +
|
||||||
|
" and nv(u.status) <> 'done';\n" +
|
||||||
|
" elsif r_lp.total > 0\n" +
|
||||||
|
" then\n" +
|
||||||
|
" update core.process u\n" +
|
||||||
|
" set status = 'open'\n" +
|
||||||
|
" where\n" +
|
||||||
|
" u.rid_process = r_lp.rid_process\n" +
|
||||||
|
" and nv(u.status) <> 'open';\n" +
|
||||||
|
"\n" +
|
||||||
|
" end if;\n" +
|
||||||
|
"$$;\n"
|
||||||
|
|
||||||
|
got := format(src)
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("issue #1 PL/pgSQL indenting\n--- got ---\n%s\n--- want ---\n%s", got, want)
|
||||||
|
}
|
||||||
|
checkDML(t, "issue #1 PL/pgSQL indenting", got)
|
||||||
|
if !semanticallyEqual(src, got) {
|
||||||
|
t.Errorf("issue #1 PL/pgSQL indenting changed semantics")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCorpusIdempotentAndSafe(t *testing.T) {
|
func TestCorpusIdempotentAndSafe(t *testing.T) {
|
||||||
dir := filepath.Join("..", "..", "testdata", "corpus")
|
dir := filepath.Join("..", "..", "testdata", "corpus")
|
||||||
entries, err := os.ReadDir(dir)
|
entries, err := os.ReadDir(dir)
|
||||||
|
|||||||
Vendored
+18
-18
@@ -632,7 +632,7 @@ BEGIN
|
|||||||
end if;*/
|
end if;*/
|
||||||
|
|
||||||
if G_BENCHMARK = 1
|
if G_BENCHMARK = 1
|
||||||
then
|
then
|
||||||
perform log_event(m_funcname,format('Perf Merge Replace SinceStart: %s Duration: %s', clock_timestamp() - m_start, clock_timestamp() - m_ltime),bt_enum('eventlog','local notice'));
|
perform log_event(m_funcname,format('Perf Merge Replace SinceStart: %s Duration: %s', clock_timestamp() - m_start, clock_timestamp() - m_ltime),bt_enum('eventlog','local notice'));
|
||||||
m_ltime = clock_timestamp();
|
m_ltime = clock_timestamp();
|
||||||
end if;
|
end if;
|
||||||
@@ -735,7 +735,7 @@ BEGIN
|
|||||||
, '\[(.*?)\]', 'ig') r(v) into m_errmsg,m_retval;
|
, '\[(.*?)\]', 'ig') r(v) into m_errmsg,m_retval;
|
||||||
|
|
||||||
if m_retval > 0
|
if m_retval > 0
|
||||||
then
|
then
|
||||||
raise exception E'The following data fields could not be found for prefix % \r\n%', ifblnk(m_data_prefix, 'Null'), m_errmsg using hint = 'in ID replace process';
|
raise exception E'The following data fields could not be found for prefix % \r\n%', ifblnk(m_data_prefix, 'Null'), m_errmsg using hint = 'in ID replace process';
|
||||||
end if;
|
end if;
|
||||||
|
|
||||||
@@ -1136,16 +1136,16 @@ BEGIN
|
|||||||
-- );
|
-- );
|
||||||
|
|
||||||
if r_lp_t.table_name = any(a_inner_selected) and nv(r_lp_t.table_name ) <> ''
|
if r_lp_t.table_name = any(a_inner_selected) and nv(r_lp_t.table_name ) <> ''
|
||||||
then
|
then
|
||||||
raise notice 'Table used as inner table: %', r_lp_t.table_name;
|
raise notice 'Table used as inner table: %', r_lp_t.table_name;
|
||||||
continue;
|
continue;
|
||||||
end if;
|
end if;
|
||||||
--raise notice 'Field:% Table:% Tag: % Type: %', r_lp_t.field_name, r_lp_t.table_name, r_lp_t.mergetag, r_lp_t.merge_type;
|
--raise notice 'Field:% Table:% Tag: % Type: %', r_lp_t.field_name, r_lp_t.table_name, r_lp_t.mergetag, r_lp_t.merge_type;
|
||||||
if nv(r_lp_t.field_name) = '' and r_lp_t.merge_type not in (G_MTYPE_TBLROOT,G_MTYPE_SPECIAL)
|
if nv(r_lp_t.field_name) = '' and r_lp_t.merge_type not in (G_MTYPE_TBLROOT,G_MTYPE_SPECIAL)
|
||||||
then
|
then
|
||||||
|
|
||||||
if G_DEBUG
|
if G_DEBUG
|
||||||
then
|
then
|
||||||
perform log_event(m_funcname,format('Blank field name on Complex merge for p_doctype=%s, p_commtype=%s, p_data_prefix=%s, p_data_rid=%s
|
perform log_event(m_funcname,format('Blank field name on Complex merge for p_doctype=%s, p_commtype=%s, p_data_prefix=%s, p_data_rid=%s
|
||||||
field_name=%s, merge_type=%s, table_name=%s
|
field_name=%s, merge_type=%s, table_name=%s
|
||||||
' ,p_doctype, p_commtype,p_data_prefix,p_data_rid
|
' ,p_doctype, p_commtype,p_data_prefix,p_data_rid
|
||||||
@@ -1159,7 +1159,7 @@ BEGIN
|
|||||||
end if;
|
end if;
|
||||||
|
|
||||||
if nv(m_exec_orderstr) = '' and nv(r_lp_t.parent_order_string) <> ''
|
if nv(m_exec_orderstr) = '' and nv(r_lp_t.parent_order_string) <> ''
|
||||||
then
|
then
|
||||||
m_exec_orderstr = r_lp_t.parent_order_string;
|
m_exec_orderstr = r_lp_t.parent_order_string;
|
||||||
--raise notice 'Applying order % by for % %.', r_lp_t.parent_order_string, r_lp_t.parent_table_name,r_lp_t.field_name;
|
--raise notice 'Applying order % by for % %.', r_lp_t.parent_order_string, r_lp_t.parent_table_name,r_lp_t.field_name;
|
||||||
/*
|
/*
|
||||||
@@ -1176,7 +1176,7 @@ BEGIN
|
|||||||
end if;
|
end if;
|
||||||
|
|
||||||
if nv(r_lp_t.ops_string) = ''
|
if nv(r_lp_t.ops_string) = ''
|
||||||
then
|
then
|
||||||
r_lp_t.ops_string = r_lp_t.field_name;
|
r_lp_t.ops_string = r_lp_t.field_name;
|
||||||
end if;
|
end if;
|
||||||
|
|
||||||
@@ -1192,19 +1192,19 @@ BEGIN
|
|||||||
end loop;
|
end loop;
|
||||||
|
|
||||||
if r_lp_t.merge_type in (G_MTYPE_TBLFIELD, G_MTYPE_CONDFIELD)
|
if r_lp_t.merge_type in (G_MTYPE_TBLFIELD, G_MTYPE_CONDFIELD)
|
||||||
then
|
then
|
||||||
m_execstr = format($S$%s|| '%s"%s":' || json_build_object('value',json_agg(%s::text %s), 'type', '%s')::text %s$S$
|
m_execstr = format($S$%s|| '%s"%s":' || json_build_object('value',json_agg(%s::text %s), 'type', '%s')::text %s$S$
|
||||||
,m_execstr,m_comma,r_lp_t.mergetag
|
,m_execstr,m_comma,r_lp_t.mergetag
|
||||||
,r_lp_t.ops_string
|
,r_lp_t.ops_string
|
||||||
, m_exec_orderstr, r_lp_t.merge_type, E'\r\n');
|
, m_exec_orderstr, r_lp_t.merge_type, E'\r\n');
|
||||||
|
|
||||||
elseif r_lp_t.merge_type = G_MTYPE_SPECIAL--special fields
|
elseif r_lp_t.merge_type = G_MTYPE_SPECIAL--special fields
|
||||||
then
|
then
|
||||||
m_execstr = format($S$%s|| '%s"%s":' || json_build_object('value',%s, 'type', '%s')::text %s$S$
|
m_execstr = format($S$%s|| '%s"%s":' || json_build_object('value',%s, 'type', '%s')::text %s$S$
|
||||||
,m_execstr,m_comma,r_lp_t.mergetag,quote_literal(r_lp_t.tagvalue), r_lp_t.merge_type, E'\r\n');
|
,m_execstr,m_comma,r_lp_t.mergetag,quote_literal(r_lp_t.tagvalue), r_lp_t.merge_type, E'\r\n');
|
||||||
--raise notice 'Special Field: %s',r_lp_t;
|
--raise notice 'Special Field: %s',r_lp_t;
|
||||||
elseif r_lp.merge_type = G_MTYPE_PICTURE
|
elseif r_lp.merge_type = G_MTYPE_PICTURE
|
||||||
then
|
then
|
||||||
m_execstr = format($S$%s|| '%s"%s":'
|
m_execstr = format($S$%s|| '%s"%s":'
|
||||||
|| json_build_object('value',%s::text, 'type', '%s'
|
|| json_build_object('value',%s::text, 'type', '%s'
|
||||||
, 'w', mailmerge_specialfield('width', '%s', %s) ,'h', mailmerge_specialfield('height', '%s', %s))::text %s$S$
|
, 'w', mailmerge_specialfield('width', '%s', %s) ,'h', mailmerge_specialfield('height', '%s', %s))::text %s$S$
|
||||||
@@ -1212,7 +1212,7 @@ BEGIN
|
|||||||
,r_lp.mergetag,quote_nullable(m_data_rid),r_lp.mergetag,quote_nullable(m_data_rid), E'\r\n');
|
,r_lp.mergetag,quote_nullable(m_data_rid),r_lp.mergetag,quote_nullable(m_data_rid), E'\r\n');
|
||||||
|
|
||||||
elseif nv(r_lp_t.field_name) <> ''
|
elseif nv(r_lp_t.field_name) <> ''
|
||||||
then
|
then
|
||||||
m_execstr = format($S$%s|| '%s"%s":' || json_build_object('value',%s::text, 'type', '%s')::text %s$S$
|
m_execstr = format($S$%s|| '%s"%s":' || json_build_object('value',%s::text, 'type', '%s')::text %s$S$
|
||||||
,m_execstr,m_comma,r_lp_t.mergetag
|
,m_execstr,m_comma,r_lp_t.mergetag
|
||||||
, r_lp_t.ops_string
|
, r_lp_t.ops_string
|
||||||
@@ -1225,7 +1225,7 @@ BEGIN
|
|||||||
m_blankexec = format($S$%s|| '%s"%s":' || json_build_object('value','', 'type', '%s')::text %s$S$,m_blankexec,m_comma,r_lp_t.mergetag, r_lp_t.merge_type, E'\r\n');
|
m_blankexec = format($S$%s|| '%s"%s":' || json_build_object('value','', 'type', '%s')::text %s$S$,m_blankexec,m_comma,r_lp_t.mergetag, r_lp_t.merge_type, E'\r\n');
|
||||||
|
|
||||||
if r_lp_t.rn = 1
|
if r_lp_t.rn = 1
|
||||||
then
|
then
|
||||||
--Inner level tables (2)
|
--Inner level tables (2)
|
||||||
--raise notice 'Begin: parent: %', r_lp_t;
|
--raise notice 'Begin: parent: %', r_lp_t;
|
||||||
for r_lp_c in (
|
for r_lp_c in (
|
||||||
@@ -1345,7 +1345,7 @@ BEGIN
|
|||||||
end loop;
|
end loop;
|
||||||
|
|
||||||
if ifblnk(r_lp_t.parent_table_name,'') = ''
|
if ifblnk(r_lp_t.parent_table_name,'') = ''
|
||||||
then
|
then
|
||||||
m_execstr = format(E'select (''{'' %s \r\n || ''}'')::json ;',m_execstr );
|
m_execstr = format(E'select (''{'' %s \r\n || ''}'')::json ;',m_execstr );
|
||||||
else
|
else
|
||||||
select string_agg(s.filter_string, ' ')
|
select string_agg(s.filter_string, ' ')
|
||||||
@@ -1364,7 +1364,7 @@ BEGIN
|
|||||||
from exec_json(m_execstr, 'str json') r into m_retval,m_errmsg, m_json;
|
from exec_json(m_execstr, 'str json') r into m_retval,m_errmsg, m_json;
|
||||||
|
|
||||||
if m_json is null
|
if m_json is null
|
||||||
then
|
then
|
||||||
|
|
||||||
select r.p_retval, r.p_errmsg, r.p_json - > 'str'
|
select r.p_retval, r.p_errmsg, r.p_json - > 'str'
|
||||||
from exec_json(m_execstr, 'str json') r into m_retval,m_errmsg, m_json;
|
from exec_json(m_execstr, 'str json') r into m_retval,m_errmsg, m_json;
|
||||||
@@ -1373,12 +1373,12 @@ BEGIN
|
|||||||
m_debug_exestr = nv(m_debug_exestr) || E'\r\n/*'|| nv(r_lp_t.parent_table_name) || ' len:' || nv(length(m_json::text)) ||E'*/ \r\n' || nv(m_execstr) || E'\r\n ';
|
m_debug_exestr = nv(m_debug_exestr) || E'\r\n/*'|| nv(r_lp_t.parent_table_name) || ' len:' || nv(length(m_json::text)) ||E'*/ \r\n' || nv(m_execstr) || E'\r\n ';
|
||||||
|
|
||||||
if m_json_full_complex is null
|
if m_json_full_complex is null
|
||||||
then
|
then
|
||||||
m_json_full_complex = jsonb_build_object(r_lp_t.tblid::text,m_json);
|
m_json_full_complex = jsonb_build_object(r_lp_t.tblid::text,m_json);
|
||||||
end if;
|
end if;
|
||||||
|
|
||||||
if (m_json_full_complex->r_lp_t.tblid::text) is null
|
if (m_json_full_complex->r_lp_t.tblid::text) is null
|
||||||
then
|
then
|
||||||
m_json_full_complex = jsonb_set(m_json_full_complex, format('{%s}',r_lp_t.tblid)::text[], m_json::jsonb,true);
|
m_json_full_complex = jsonb_set(m_json_full_complex, format('{%s}',r_lp_t.tblid)::text[], m_json::jsonb,true);
|
||||||
else
|
else
|
||||||
m_json_full_complex = jsonb_set(m_json_full_complex, format('{%s}',r_lp_t.tblid)::text[], _jsonb_object_cat(m_json_full_complex->r_lp_t.tblid,m_json::jsonb),true);
|
m_json_full_complex = jsonb_set(m_json_full_complex, format('{%s}',r_lp_t.tblid)::text[], _jsonb_object_cat(m_json_full_complex->r_lp_t.tblid,m_json::jsonb),true);
|
||||||
@@ -1399,13 +1399,13 @@ BEGIN
|
|||||||
end if;
|
end if;
|
||||||
|
|
||||||
if nv(m_comma) = '' and length(m_execstr) > 2
|
if nv(m_comma) = '' and length(m_execstr) > 2
|
||||||
then
|
then
|
||||||
m_comma = ',';
|
m_comma = ',';
|
||||||
end if;
|
end if;
|
||||||
end loop;
|
end loop;
|
||||||
|
|
||||||
if G_DEBUG
|
if G_DEBUG
|
||||||
then
|
then
|
||||||
perform pl_writefile(r_template.debugsql_filename, convert_to(m_debug_exestr,'utf8'));
|
perform pl_writefile(r_template.debugsql_filename, convert_to(m_debug_exestr,'utf8'));
|
||||||
end if;
|
end if;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user