Files
2026-07-13 12:32:25 +08:00

796 lines
26 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package plpgsql
import (
"fmt"
"strconv"
"strings"
"github.com/cockroachdb/errors"
)
// action exists to match the expected JSON format.
type action struct {
StmtBlock plpgSQL_stmt_block `json:"PLpgSQL_stmt_block"`
}
// cond exists to match the expected JSON format.
type cond struct {
Expression plpgSQL_expr `json:"PLpgSQL_expr"`
}
// datatype exists to match the expected JSON format.
type datatype struct {
Type plpgSQL_type `json:"PLpgSQL_type"`
}
// default_val exists to match the expected JSON format.
type default_val struct {
Var plpgSQL_expr `json:"PLpgSQL_expr"`
}
// datum exists to match the expected JSON format.
type datum struct {
Record *plpgSQL_rec `json:"PLpgSQL_rec"`
RecordField *plpgSQL_recfield `json:"PLpgSQL_recfield"`
Row *plpgSQL_row `json:"PLpgSQL_row"`
Variable *plpgSQL_var `json:"PLpgSQL_var"`
}
// elsif exists to match the expected JSON format.
type elsif struct {
ElseIf plpgSQL_if_elsif `json:"PLpgSQL_if_elsif"`
}
// expr exists to match the expected JSON format.
type expr struct {
Expression plpgSQL_expr `json:"PLpgSQL_expr"`
}
// field exists to match the expected JSON format.
type field struct {
Name string `json:"name"`
VariableNumber int32 `json:"varno"`
}
// function exists to match the expected JSON format.
type function struct {
Function plpgSQL_block `json:"PLpgSQL_function"`
}
// plpgSQL_block exists to match the expected JSON format.
type plpgSQL_block struct {
NewVariableNumber int32 `json:"new_varno"`
OldVariableNumber int32 `json:"old_varno"`
Datums []datum `json:"datums"`
Action action `json:"action"`
}
// plpgSQL_expr exists to match the expected JSON format.
type plpgSQL_expr struct {
Query string `json:"query"`
ParseMode int32 `json:"parseMode"`
}
// plpgSQL_if_elsif exists to match the expected JSON format.
type plpgSQL_if_elsif struct {
Condition cond `json:"cond"`
Then []statement `json:"stmts"`
LineNumber int32 `json:"lineno"`
}
// plpgSQL_rec exists to match the expected JSON format.
type plpgSQL_rec struct {
RefName string `json:"refname"`
DatumNumber int32 `json:"dno"`
}
// plpgSQL_recfield exists to match the expected JSON format.
type plpgSQL_recfield struct {
FieldName string `json:"fieldname"`
RecordParentNumber int32 `json:"recparentno"`
}
// plpgSQL_row exists to match the expected JSON format.
type plpgSQL_row struct {
RefName string `json:"refname"`
Fields []field `json:"fields"`
LineNumber int32 `json:"lineno"`
}
// plpgSQL_stmt_assign exists to match the expected JSON format.
type plpgSQL_stmt_assign struct {
Expression expr `json:"expr"`
VariableNumber int32 `json:"varno"`
LineNumber int32 `json:"lineno"`
}
// plpgSQL_stmt_block exists to match the expected JSON format.
type plpgSQL_stmt_block struct {
Body []statement `json:"body"`
Label string `json:"label"`
LineNumber int32 `json:"lineno"`
}
// plpgSQL_stmt_call exists to match the expected JSON format.
type plpgSQL_stmt_call struct {
LineNumber int32 `json:"lineno"`
Expression expr `json:"expr"`
IsCall bool `json:"is_call"`
Target datum `json:"target"`
}
// plpgSQL_stmt_case exists to match the expected JSON format.
type plpgSQL_stmt_case struct {
LineNumber int32 `json:"lineno"`
Expression expr `json:"t_expr"`
// VarNo indicates the ID for the __Case__Variable_N__ variable that holds the evaluated
// value of the case expression.
VarNo int32 `json:"t_varno"`
WhenList []statement `json:"case_when_list"`
HasElse bool `json:"have_else"`
Else []statement `json:"else_stmts"`
}
// plpgSQL_stmt_dynexecute exists to match the expected JSON format.
type plpgSQL_stmt_dynexecute struct {
LineNumber int32 `json:"lineno"`
Into bool `json:"into"`
Query expr `json:"query"`
Target datum `json:"target"`
Params []sqlstmt `json:"params"`
}
// plpgSQL_case_when exists to match the expected JSON format.
type plpgSQL_case_when struct {
LineNumber int32 `json:"lineno"`
Expression expr `json:"expr"`
Body []statement `json:"stmts"`
}
// plpgSQL_stmt_execsql exists to match the expected JSON format.
type plpgSQL_stmt_execsql struct {
SQLStmt sqlstmt `json:"sqlstmt"`
LineNumber int32 `json:"lineno"`
Into bool `json:"into"`
Target datum `json:"target"`
}
// plpgSQL_stmt_exit exists to match the expected JSON format.
type plpgSQL_stmt_exit struct {
Label string `json:"label"`
IsExit bool `json:"is_exit"`
Condition *expr `json:"cond"`
LineNumber int32 `json:"lineno"`
}
// plpgSQL_stmt_fori exists to match the expected JSON format.
type plpgSQL_stmt_fori struct {
Label string `json:"label"`
Var datum `json:"var"`
Lower *expr `json:"lower"`
Upper *expr `json:"upper"`
Step *expr `json:"step"`
Reverse bool `json:"reverse"`
Body []statement `json:"body"`
LineNumber int32 `json:"lineno"`
}
// plpgSQL_stmt_fors exists to match the expected JSON format.
type plpgSQL_stmt_fors struct {
Label string `json:"label"`
Var datum `json:"var"`
Body []statement `json:"body"`
Query *expr `json:"query"`
LineNumber int32 `json:"lineno"`
}
// plpgSQL_stmt_if exists to match the expected JSON format.
type plpgSQL_stmt_if struct {
Condition cond `json:"cond"`
Then []statement `json:"then_body"`
ElseIf []elsif `json:"elsif_list"`
Else []statement `json:"else_body"`
LineNumber int32 `json:"lineno"`
}
// plpgSQL_stmt_loop exists to match the expected JSON format.
type plpgSQL_stmt_loop struct {
Body []statement `json:"body"`
Label string `json:"label"`
LineNumber int32 `json:"lineno"`
}
// plpgSQL_stmt_perform exists to match the expected JSON format.
type plpgSQL_stmt_perform struct {
Expression expr `json:"expr"`
LineNumber int32 `json:"lineno"`
}
// plpgSQL_stmt_raise exists to match the expected JSON format.
type plpgSQL_stmt_raise struct {
LineNumber int32 `json:"lineno"`
ELogLevel int32 `json:"elog_level"`
Message string `json:"message"`
Params []sqlstmt `json:"params"`
Options []plpgSQL_raise_option_wrapper `json:"options"`
}
// plpgSQL_raise_option_wrapper exists to match the expected JSON format.
type plpgSQL_raise_option_wrapper struct {
Option plpgSQL_raise_option `json:"PLpgSQL_raise_option"`
}
// plpgSQL_raise_option exists to match the expected JSON format.
type plpgSQL_raise_option struct {
OptionType int32 `json:"opt_type"`
Expression sqlstmt `json:"expr"`
}
// plpgSQL_stmt_return exists to match the expected JSON format.
type plpgSQL_stmt_return struct {
Expression expr `json:"expr"`
LineNumber int32 `json:"lineno"`
}
// plpgSQL_stmt_return_query exists to match the expected JSON format.
type plpgSQL_stmt_return_query struct {
Query expr `json:"query"`
LineNumber int32 `json:"lineno"`
}
// plpgSQL_stmt_while exists to match the expected JSON format.
type plpgSQL_stmt_while struct {
Condition cond `json:"cond"`
Body []statement `json:"body"`
Label string `json:"label"`
LineNumber int32 `json:"lineno"`
}
// plpgSQL_type exists to match the expected JSON format.
type plpgSQL_type struct {
Name string `json:"typname"`
}
// plpgSQL_var exists to match the expected JSON format.
type plpgSQL_var struct {
RefName string `json:"refname"`
Type datatype `json:"datatype"`
LineNumber int32 `json:"lineno"`
Default default_val `json:"default_val"`
}
// sqlstmt exists to match the expected JSON format.
type sqlstmt struct {
Expr plpgSQL_expr `json:"PLpgSQL_expr"`
}
// statement exists to match the expected JSON format. Unlike other structs, this is used like a union rather than
// having a singular expected implementation.
type statement struct {
Assignment *plpgSQL_stmt_assign `json:"PLpgSQL_stmt_assign"`
Block *plpgSQL_stmt_block `json:"PLpgSQL_stmt_block"`
Call *plpgSQL_stmt_call `json:"PLpgSQL_stmt_call"`
Case *plpgSQL_stmt_case `json:"PLpgSQL_stmt_case"`
DynExec *plpgSQL_stmt_dynexecute `json:"PLpgSQL_stmt_dynexecute"`
ExecSQL *plpgSQL_stmt_execsql `json:"PLpgSQL_stmt_execsql"`
Exit *plpgSQL_stmt_exit `json:"PLpgSQL_stmt_exit"`
ForILoop *plpgSQL_stmt_fori `json:"PLpgSQL_stmt_fori"`
ForSLoop *plpgSQL_stmt_fors `json:"PLpgSQL_stmt_fors"`
If *plpgSQL_stmt_if `json:"PLpgSQL_stmt_if"`
Loop *plpgSQL_stmt_loop `json:"PLpgSQL_stmt_loop"`
Perform *plpgSQL_stmt_perform `json:"PLpgSQL_stmt_perform"`
Raise *plpgSQL_stmt_raise `json:"PLpgSQL_stmt_raise"`
Return *plpgSQL_stmt_return `json:"PLpgSQL_stmt_return"`
ReturnQuery *plpgSQL_stmt_return_query `json:"PLpgSQL_stmt_return_query"`
When *plpgSQL_case_when `json:"PLpgSQL_case_when"`
While *plpgSQL_stmt_while `json:"PLpgSQL_stmt_while"`
}
// Convert converts the JSON statement into its output form.
func (stmt *plpgSQL_stmt_assign) Convert() (Assignment, error) {
query := stmt.Expression.Expression.Query
varName := ""
if equalsIdx := strings.Index(query, ":="); equalsIdx > 0 {
varName = strings.TrimSpace(query[:equalsIdx])
query = strings.TrimSpace(query[equalsIdx+2:])
} else if equalsIdx = strings.Index(query, "="); equalsIdx > 0 {
varName = strings.TrimSpace(query[:equalsIdx])
query = strings.TrimSpace(query[equalsIdx+1:])
} else {
return Assignment{}, errors.New("PL/pgSQL assignment cannot find `:=` sign")
}
return Assignment{
VariableName: varName,
Expression: query,
VariableIndex: stmt.VariableNumber,
}, nil
}
// Convert converts the JSON statement into its output form.
func (stmt *plpgSQL_stmt_call) Convert() (ExecuteSQL, error) {
var target string
if !stmt.IsCall {
if stmt.Target.Row != nil {
names := make([]string, len(stmt.Target.Row.Fields))
for i, rowField := range stmt.Target.Row.Fields {
names[i] = rowField.Name
}
target = strings.Join(names, ",")
} else if stmt.Target.Variable != nil {
target = stmt.Target.Variable.RefName
} else {
return ExecuteSQL{}, errors.Errorf("unhandled datum type: %T", stmt.Target)
}
}
return ExecuteSQL{
Statement: stmt.Expression.Expression.Query,
Target: target,
}, nil
}
// Convert converts the JSON statement into its output form.
func (stmt *plpgSQL_stmt_case) Convert() (block Block, err error) {
// If the CASE statement has a main expression, start by assigning it to a variable so
// we can evaluate it once and only once.
if stmt.Expression.Expression.Query != "" {
// TODO: pg_query_go creates the definitions for these variables, and
// ideally users shouldn't be able to reference them. We could
// update all the references to them (i.e. declaration, assignment,
// and WHEN block exprs) to change the name to include a \0 char to
// prevent users from referencing them or colliding with them.
block.Body = append(block.Body, Assignment{
VariableName: fmt.Sprintf("__Case__Variable_%d__", stmt.VarNo),
Expression: stmt.Expression.Expression.Query,
})
}
// Record indexes of all the GOTO ops that jump to the very end of the case block so we
// can update them later and plug in the correct offsets after we know the final size.
var gotoEndOpsIndexes []int
// Add operations for each WHEN statement...
for _, stmt := range stmt.WhenList {
when := stmt.When
if when == nil {
return Block{}, fmt.Errorf("case statement WHEN clause is nil")
}
// TODO: The generated expressions from pg_query_go uses double quotes
// around the variable name, which is valid for Postgres, but
// our engine doesn't currently resolve double-quoted strings to
// variables, so for now, we just extract the double quotes.
expressionString := when.Expression.Expression.Query
expressionString = strings.ReplaceAll(expressionString, `"`, "")
convertedWhenBodyStatements, err := jsonConvertStatements(when.Body)
if err != nil {
return Block{}, err
}
block.Body = append(block.Body,
If{
Condition: expressionString,
GotoOffset: 2,
},
Goto{
// This GOTO jumps to the next WHEN block, so step over all the statements
// from this WHEN block, plus 1 for the GOTO op we add at the end of each
// block, and plus 1 more to move to the next statement.
Offset: int32(len(convertedWhenBodyStatements) + 1 + 1),
})
block.Body = append(block.Body, convertedWhenBodyStatements...)
// Add a GOTO op to jump to the end of the entire CASE block, and record its position
// in the statement block so we can update it later.
block.Body = append(block.Body, Goto{})
gotoEndOpsIndexes = append(gotoEndOpsIndexes, len(block.Body)-1)
}
if stmt.HasElse {
convertElseBodyStatements, err := jsonConvertStatements(stmt.Else)
if err != nil {
return Block{}, err
}
block.Body = append(block.Body, convertElseBodyStatements...)
// TODO: If no cases match and there is no ELSE block, then add a RAISE statement
// to return an error.
//} else {
// Sample PostgreSQL error response:
// ERROR: case not found
// HINT: CASE statement is missing ELSE part.
// CONTEXT: PL/pgSQL function interpreted_case(integer) line 5 at CASE
}
// Update all the GOTO ops that jump to the very end of the case block.
for _, gotoEndOpIndex := range gotoEndOpsIndexes {
// Sanity check that we are looking at a GOTO statement
if _, ok := block.Body[gotoEndOpIndex].(Goto); !ok {
return Block{}, fmt.Errorf("expected Goto statement, got %T", block.Body[gotoEndOpIndex])
}
block.Body[gotoEndOpIndex] = Goto{
Offset: int32(len(block.Body) - gotoEndOpIndex),
}
}
return block, nil
}
// Convert converts the JSON statement into its output form.
func (stmt *plpgSQL_stmt_dynexecute) Convert() (DynamicExecute, error) {
var params []string
for _, param := range stmt.Params {
params = append(params, param.Expr.Query)
}
var target string
if stmt.Into {
switch {
case stmt.Target.Row != nil:
names := make([]string, len(stmt.Target.Row.Fields))
for i, rowField := range stmt.Target.Row.Fields {
names[i] = rowField.Name
}
target = strings.Join(names, ",")
case stmt.Target.Variable != nil:
target = stmt.Target.Variable.RefName
default:
return DynamicExecute{}, errors.Errorf("unhandled datum type: %T", stmt.Target)
}
}
query := strings.TrimSuffix(strings.TrimPrefix(stmt.Query.Expression.Query, "'"), "'")
return DynamicExecute{
Query: query,
Params: params,
Target: target,
}, nil
}
// Convert converts the JSON statement into its output form.
func (stmt *plpgSQL_stmt_execsql) Convert() (ExecuteSQL, error) {
var target string
if stmt.Into {
if stmt.Target.Row != nil {
names := make([]string, len(stmt.Target.Row.Fields))
for i, rowField := range stmt.Target.Row.Fields {
names[i] = rowField.Name
}
target = strings.Join(names, ",")
} else if stmt.Target.Variable != nil {
target = stmt.Target.Variable.RefName
} else {
return ExecuteSQL{}, errors.Errorf("unhandled datum type: %T", stmt.Target)
}
}
return ExecuteSQL{
Statement: stmt.SQLStmt.Expr.Query,
Target: target,
}, nil
}
// Convert converts the JSON statement into its output form.
func (stmt *plpgSQL_stmt_exit) Convert() Statement {
offset := int32(-1)
if stmt.IsExit {
offset = 1
}
var gotoStmt Goto
if len(stmt.Label) > 0 {
gotoStmt = Goto{
Offset: offset,
Label: stmt.Label,
}
} else {
gotoStmt = Goto{
Offset: offset,
NearestScopeOp: true,
}
}
if stmt.Condition == nil {
return gotoStmt
} else {
return Block{
Body: []Statement{
If{
Condition: stmt.Condition.Expression.Query,
GotoOffset: 2,
},
Goto{Offset: 2},
gotoStmt,
},
}
}
}
// Convert converts the JSON statement into its output form.
func (stmt *plpgSQL_stmt_fori) Convert() (block Block, err error) {
block.Label = stmt.Label
block.IsLoop = true
if stmt.Var.Variable == nil {
return Block{}, errors.New("for loop variable cannot be nil")
}
varName := stmt.Var.Variable.RefName
// Extract bound and step expressions
lowerExpr := "1"
if stmt.Lower != nil {
lowerExpr = stmt.Lower.Expression.Query
}
upperExpr := "1"
if stmt.Upper != nil {
upperExpr = stmt.Upper.Expression.Query
}
stepExpr := "1"
if stmt.Step != nil {
stepExpr = stmt.Step.Expression.Query
}
// Determine init value, loop condition, and increment expression based on direction.
// In the JSON, Lower is always the starting value and Upper is the ending bound.
var condition, incrExpr string
if stmt.Reverse {
condition = fmt.Sprintf("%s >= (%s)", varName, upperExpr)
incrExpr = fmt.Sprintf("%s - (%s)", varName, stepExpr)
} else {
condition = fmt.Sprintf("%s <= (%s)", varName, upperExpr)
incrExpr = fmt.Sprintf("%s + (%s)", varName, stepExpr)
}
// Convert the loop body.
convertedBody, err := jsonConvertStatements(stmt.Body)
if err != nil {
return Block{}, err
}
bodySize := OperationSizeForStatements(convertedBody)
// Build the loop body:
// [0] InitAssign: varName := lower
// [1] If(condition, GotoOffset:2) → jumps to [3] (first body stmt) when true
// [2] ExitGoto → offset=3+bodySize → jumps to ScopeEnd
// [3..3+N-1] body statements (N = bodySize)
// [3+N] IncrAssign: varName := varName +/- step
// [3+N+1] BackGoto → offset=-(3+bodySize) → jumps back to If at [1]
//
// Because no variables are declared in this block (the loop variable is already
// declared by the caller's DECLARE section), ScopeBegin is at M and the
// InitAssign is at M+1, so all offsets are consistent.
block.Body = []Statement{
Assignment{
VariableName: varName,
Expression: lowerExpr,
},
If{
Condition: condition,
GotoOffset: 2,
},
Goto{
Offset: 3 + bodySize,
},
}
block.Body = append(block.Body, convertedBody...)
block.Body = append(block.Body,
Assignment{
VariableName: varName,
Expression: incrExpr,
},
Goto{
Offset: -(3 + bodySize),
},
)
return block, nil
}
// Convert converts the JSON statement into its output form.
func (stmt *plpgSQL_stmt_fors) Convert() (block Block, err error) {
block.Label = stmt.Label
block.IsLoop = true
if stmt.Query == nil {
return Block{}, errors.New("FOR..IN..SELECT loop must have a query")
}
var varName string
switch {
case stmt.Var.Record != nil:
varName = stmt.Var.Record.RefName
case stmt.Var.Variable != nil:
varName = stmt.Var.Variable.RefName
case stmt.Var.Row != nil:
varName = stmt.Var.Row.RefName
default:
return Block{}, errors.New("FOR..IN..SELECT loop variable must be a record, row, or variable")
}
// Use the line number to keep cursor names unique across multiple ForS loops
// that might use the same variable name.
cursorName := fmt.Sprintf("__cursor_%s_%d__", varName, stmt.LineNumber)
query := stmt.Query.Expression.Query
convertedBody, err := jsonConvertStatements(stmt.Body)
if err != nil {
return Block{}, err
}
bodySize := OperationSizeForStatements(convertedBody)
// Layout inside the block (ScopeBegin/ScopeEnd are added by Block.AppendOperations):
// [0] ForQueryInit execute query, store rows in cursor
// [1] ForQueryNext fetch next row into varName, or jump forward by (bodySize+2) to ScopeEnd
// [2..2+bodySize-1] body statements
// [2+bodySize] Goto back to ForQueryNext: offset = -(1 + bodySize)
block.Body = []Statement{
ForQueryInit{CursorName: cursorName, Query: query},
ForQueryNext{CursorName: cursorName, RecordVar: varName, GotoOffset: bodySize + 2},
}
block.Body = append(block.Body, convertedBody...)
block.Body = append(block.Body, Goto{Offset: -(1 + bodySize)})
return block, nil
}
// Convert converts the JSON statement into its output form.
func (stmt *plpgSQL_stmt_if) Convert() (Block, error) {
// We store all GOTOs that will need to go to the end of the block. Since we can't know that ahead of time, we store
// their indexes and set them at the end of the function.
type gotoEndIndex struct {
BodyIndex int
GotoIndex int32
}
var gotoEndIndexes []gotoEndIndex
returnBlock := Block{
Body: []Statement{
If{
Condition: stmt.Condition.Expression.Query,
GotoOffset: 2, // The operation following the conditional skips the THEN statements, so we're skipping that
},
},
}
// We'll parse our THEN statements, but we won't add them to the block just yet as we need their operation sizes
thenStmts, err := jsonConvertStatements(stmt.Then)
if err != nil {
return Block{}, err
}
// When the condition is false, we want to skip our THEN block, so we do that (plus the GOTO which finishes the THEN block)
returnBlock.Body = append(returnBlock.Body, Goto{Offset: OperationSizeForStatements(thenStmts) + 2})
// Then we'll append our THEN block
returnBlock.Body = append(returnBlock.Body, thenStmts...)
// Then we want to append the GOTO that finishes the THEN block, but we don't know the end just yet, so we'll save
// its index and fill it in later
gotoEndIndexes = append(gotoEndIndexes, gotoEndIndex{
BodyIndex: len(returnBlock.Body),
GotoIndex: OperationSizeForStatements(returnBlock.Body),
})
returnBlock.Body = append(returnBlock.Body, Goto{})
// We repeat the same process for each ELSIF statement (refer to the comments above)
for _, elseIf := range stmt.ElseIf {
returnBlock.Body = append(returnBlock.Body, If{
Condition: elseIf.ElseIf.Condition.Expression.Query,
GotoOffset: 2, // Same rules as skipping our THEN statement above
})
elseIfStmts, err := jsonConvertStatements(elseIf.ElseIf.Then)
if err != nil {
return Block{}, err
}
returnBlock.Body = append(returnBlock.Body, Goto{Offset: OperationSizeForStatements(elseIfStmts) + 2})
returnBlock.Body = append(returnBlock.Body, elseIfStmts...)
gotoEndIndexes = append(gotoEndIndexes, gotoEndIndex{
BodyIndex: len(returnBlock.Body),
GotoIndex: OperationSizeForStatements(returnBlock.Body),
})
returnBlock.Body = append(returnBlock.Body, Goto{})
}
// Finally we handle our ELSE statements. We don't have a condition to check, so we don't have to append any
// additional GOTOs.
elseStmts, err := jsonConvertStatements(stmt.Else)
if err != nil {
return Block{}, err
}
returnBlock.Body = append(returnBlock.Body, elseStmts...)
// Now we'll set all of our GOTOs so that they skip to the end of the block.
// We have to take their index position into account, since we want to skip to the end from their relative position.
for _, idx := range gotoEndIndexes {
returnBlock.Body[idx.BodyIndex] = Goto{Offset: OperationSizeForStatements(returnBlock.Body) - idx.GotoIndex}
}
return returnBlock, nil
}
// Convert converts the JSON statement into its output form.
func (stmt *plpgSQL_stmt_loop) Convert() (block Block, err error) {
// Set the block's label if one was provided
block.Label = stmt.Label
block.IsLoop = true
// Convert the body of the loop first so we can determine the GOTO offset
block.Body, err = jsonConvertStatements(stmt.Body)
if err != nil {
return Block{}, err
}
// The loop returns to the beginning of the loop, skipping the body
block.Body = append(block.Body, Goto{Offset: -OperationSizeForStatements(block.Body)})
return block, nil
}
// Convert converts the JSON statement into its output form.
func (stmt *plpgSQL_stmt_perform) Convert() Perform {
return Perform{
Statement: stmt.Expression.Expression.Query,
}
}
// Convert converts the JSON statement into its output form.
func (stmt *plpgSQL_stmt_raise) Convert() Raise {
var params []string
for _, param := range stmt.Params {
params = append(params, param.Expr.Query)
}
options := make(map[string]string)
for _, option := range stmt.Options {
options[strconv.Itoa(int(option.Option.OptionType))] = option.Option.Expression.Expr.Query
}
return Raise{
Level: NoticeLevel(uint8(stmt.ELogLevel)).String(),
Message: stmt.Message,
Params: params,
Options: options,
}
}
// Convert converts the JSON statement into its output form.
func (stmt *plpgSQL_stmt_return) Convert() Return {
return Return{
Expression: stmt.Expression.Expression.Query,
}
}
// Convert converts the JSON statement into its output form.
func (stmt *plpgSQL_stmt_return_query) Convert() ReturnQuery {
return ReturnQuery{
Query: stmt.Query.Expression.Query,
}
}
// Convert converts the JSON statement into its output form.
func (stmt *plpgSQL_stmt_while) Convert() (block Block, err error) {
// Convert the body of the loop first so we can determine the GOTO offsets
convertedLoopBodyStmts, err := jsonConvertStatements(stmt.Body)
if err != nil {
return Block{}, err
}
block = Block{
Body: []Statement{
If{
Condition: stmt.Condition.Expression.Query,
// Jump forward two statements, so we skip over the GOTO below that exits the WHILE loop.
GotoOffset: 2,
},
Goto{
// Jump forward 1 statement to get to the loop body, then jump over the loop body and the
// GOTO statement that jumps to the start of the WHILE loop.
Offset: 1 + OperationSizeForStatements(convertedLoopBodyStmts) + 1,
},
},
Label: stmt.Label,
IsLoop: true,
}
// Add the converted body of the WHILE loop, and a GOTO statement that jumps backwards past the current
// GOTO statement, and past all the body statements, and past the GOTO statement at the start of the loop.
block.Body = append(block.Body, convertedLoopBodyStmts...)
block.Body = append(block.Body, Goto{Offset: -1 * (OperationSizeForStatements(convertedLoopBodyStmts) + 2)})
return block, nil
}