• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

codenotary / immudb / 24841644892

23 Apr 2026 02:44PM UTC coverage: 85.279% (-4.0%) from 89.306%
24841644892

push

gh-ci

web-flow
feat: v1.11.0 PostgreSQL compatibility and SQL feature expansion (#2090)

* Add structured audit logging with immutable audit trail

Introduces a new --audit-log flag that records all gRPC operations as
structured JSON events in immudb's tamper-proof KV store. Events are
stored under the audit: key prefix in systemdb, queryable via Scan and
verifiable via VerifiableGet. An async buffered writer ensures minimal
latency impact. Configurable event filtering (all/write/admin) via
--audit-log-events flag.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add PostgreSQL ORM compatibility layer and verification functions

Extend the pgsql wire protocol with immudb verification functions
(immudb_state, immudb_verify_row, immudb_verify_tx, immudb_history,
immudb_tx) accessible via standard SQL SELECT statements.

Add pg_catalog resolvers (pg_attribute, pg_index, pg_constraint,
pg_type, pg_settings, pg_description) and information_schema
resolvers (tables, columns, schemata, key_column_usage) to support
ORM introspection from Django, SQLAlchemy, GORM, and ActiveRecord.

Add PostgreSQL compatibility functions: current_database,
current_schema, current_user, format_type, pg_encoding_to_char,
pg_get_expr, pg_get_constraintdef, obj_description, col_description,
has_table_privilege, has_schema_privilege, and others.

Add SHOW statement emulation for common ORM config queries and
schema-qualified name stripping for information_schema and public
schema references.

* Implement EXISTS and IN subquery support in SQL engine

Replace the previously stubbed ExistsBoolExp and InSubQueryExp
implementations with working non-correlated subquery execution.

EXISTS subqueries resolve the inner SELECT and check if any rows
are returned. IN subqueries resolve the inner SELECT, iterate the
result set, and compare each value against the outer expression.
Both support NOT variants (NOT EXISTS, NOT IN).

Correlated subqueries (referencing outer query columns) ar... (continued)

7254 of 10471 new or added lines in 124 files covered. (69.28%)

115 existing lines in 18 files now uncovered.

44599 of 52298 relevant lines covered (85.28%)

127676.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.0
/pkg/pgsql/server/fmessages/bind.go
1
/*
2
Copyright 2026 Codenotary Inc. All rights reserved.
3

4
SPDX-License-Identifier: BUSL-1.1
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7

8
    https://mariadb.com/bsl11/
9

10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
15
*/
16

17
package fmessages
18

19
import (
20
        "bufio"
21
        "bytes"
22

23
        pgserrors "github.com/codenotary/immudb/pkg/pgsql/errors"
24
        "github.com/codenotary/immudb/pkg/pgsql/server/pgmeta"
25
)
26

27
// BindMsg Once a prepared statement exists, it can be readied for execution using a Bind message. The Bind message gives the
28
// name of the source prepared statement (empty string denotes the unnamed prepared statement), the name of the destination
29
// portal (empty string denotes the unnamed portal), and the values to use for any parameter placeholders present in the
30
// prepared statement. The supplied parameter set must match those needed by the prepared statement. (If you declared
31
// any void parameters in the Parse message, pass NULL values for them in the Bind message.) Bind also specifies
32
// the format to use for any data returned by the query; the format can be specified overall, or per-column.
33
// The response is either BindComplete or ErrorResponse.
34
//
35
// Note
36
// The choice between text and binary output is determined by the format codes given in Bind, regardless of the SQL
37
// command involved. The BINARY attribute in cursor declarations is irrelevant when using extended query protocol.
38
type BindMsg struct {
39
        // The name of the destination portal (an empty string selects the unnamed portal).
40
        DestPortalName string
41
        // The name of the source prepared statement (an empty string selects the unnamed prepared statement).
42
        PreparedStatementName string
43
        // The parameter format codes. Each must presently be zero (text) or one (binary).
44
        ParameterFormatCodes []int16
45
        // Array of the values of the parameters, in the format indicated by the associated format code. n is the above length.
46
        ParamVals []interface{}
47
        // The result-column format codes. Each must presently be zero (text) or one (binary).
48
        ResultColumnFormatCodes []int16
49
}
50

51
func ParseBindMsg(payload []byte) (BindMsg, error) {
184✔
52
        b := bytes.NewBuffer(payload)
184✔
53
        r := bufio.NewReaderSize(b, len(payload))
184✔
54
        destPortalName, err := getNextString(r)
184✔
55
        if err != nil {
185✔
56
                return BindMsg{}, err
1✔
57
        }
1✔
58
        preparedStatement, err := getNextString(r)
183✔
59
        if err != nil {
184✔
60
                return BindMsg{}, err
1✔
61
        }
1✔
62
        // The number of parameter format codes that follow (denoted C below).
63
        // This can be zero to indicate that there are no parameters or that the parameters all use the default format (text);
64
        // or one, in which case the specified format code is applied to all parameters; or it can equal the actual number
65
        // of parameters.
66
        parameterFormatCodeNumber, err := getNextInt16(r)
182✔
67
        if err != nil {
183✔
68
                return BindMsg{}, err
1✔
69
        }
1✔
70
        if parameterFormatCodeNumber < 0 {
182✔
71
                return BindMsg{}, pgserrors.ErrMalformedMessage
1✔
72
        }
1✔
73
        parameterFormatCodes := make([]int16, parameterFormatCodeNumber)
180✔
74
        for k := 0; k < int(parameterFormatCodeNumber); k++ {
241✔
75
                p, err := getNextInt16(r)
61✔
76
                if err != nil {
62✔
77
                        return BindMsg{}, err
1✔
78
                }
1✔
79
                parameterFormatCodes[k] = p
60✔
80
        }
81
        // The number of parameter values that follow (possibly zero). This must match the number of parameters needed by the query.
82
        pCount, err := getNextInt16(r)
179✔
83
        if err != nil {
180✔
84
                return BindMsg{}, err
1✔
85
        }
1✔
86

87
        // Handling format codes: see resultColumnFormatCodesNumber property comment
88
        forceTXT := false
178✔
89
        forceBIN := false
178✔
90
        if len(parameterFormatCodes) == 0 {
326✔
91
                forceTXT = true
148✔
92
        }
148✔
93
        if len(parameterFormatCodes) == 1 {
197✔
94
                switch parameterFormatCodes[0] {
19✔
95
                case 0:
14✔
96
                        forceTXT = true
14✔
97
                case 1:
4✔
98
                        forceBIN = true
4✔
99
                default:
1✔
100
                        return BindMsg{}, pgserrors.ErrMalformedMessage
1✔
101
                }
102
        }
103

104
        if len(parameterFormatCodes) > 1 && len(parameterFormatCodes) != int(pCount) {
178✔
105
                return BindMsg{}, pgserrors.ErrMalformedMessage
1✔
106
        }
1✔
107
        totalParamLen := 0
176✔
108
        params := make([]interface{}, 0)
176✔
109
        for i := 0; i < int(pCount); i++ {
245✔
110
                pLen, err := getNextInt32(r)
69✔
111
                if err != nil {
70✔
112
                        return BindMsg{}, err
1✔
113
                }
1✔
114
                // Postgres wire protocol: pLen == -1 means SQL NULL (no value
115
                // bytes follow). Treat as nil so downstream evaluation sees a
116
                // NULL bind. Anything below -1 is malformed.
117
                if pLen == -1 {
69✔
118
                        params = append(params, nil)
1✔
119
                        continue
1✔
120
                }
121
                if pLen < 0 {
67✔
NEW
122
                        return BindMsg{}, pgserrors.ErrNegativeParameterValueLen
×
NEW
123
                }
×
124
                totalParamLen += int(pLen)
67✔
125
                if totalParamLen > pgmeta.MaxMsgSize {
68✔
126
                        return BindMsg{}, pgserrors.ErrParametersValueSizeTooLarge
1✔
127
                }
1✔
128
                pVal := make([]byte, pLen)
66✔
129
                _, err = r.Read(pVal)
66✔
130
                if err != nil {
67✔
131
                        return BindMsg{}, err
1✔
132
                }
1✔
133
                if forceTXT {
91✔
134
                        params = append(params, string(pVal))
26✔
135
                        continue
26✔
136
                }
137
                if forceBIN {
42✔
138
                        params = append(params, pVal)
3✔
139
                        continue
3✔
140
                }
141

142
                switch parameterFormatCodes[i] {
36✔
143
                case 0:
10✔
144
                        params = append(params, string(pVal))
10✔
145
                case 1:
25✔
146
                        params = append(params, pVal)
25✔
147
                default:
1✔
148
                        return BindMsg{}, pgserrors.ErrMalformedMessage
1✔
149
                }
150
        }
151
        // The number of result-column format codes that follow (denoted R below).
152
        // This can be zero to indicate that there are no result columns or that the result columns should all use the
153
        // default format (text); or one, in which case the specified format code is applied to all result columns (if any);
154
        // or it can equal the actual number of result columns of the query.
155
        resultColumnFormatCodesNumber, err := getNextInt16(r)
172✔
156
        if err != nil {
173✔
157
                return BindMsg{}, err
1✔
158
        }
1✔
159
        if resultColumnFormatCodesNumber < 0 {
173✔
160
                return BindMsg{}, pgserrors.ErrMalformedMessage
2✔
161
        }
2✔
162

163
        resultColumnFormatCodes := make([]int16, 0, resultColumnFormatCodesNumber)
169✔
164
        for k := resultColumnFormatCodesNumber; k > 0; k-- {
394✔
165
                p, err := getNextInt16(r)
225✔
166
                if err != nil {
226✔
167
                        return BindMsg{}, err
1✔
168
                }
1✔
169
                resultColumnFormatCodes = append(resultColumnFormatCodes, p)
224✔
170
        }
171

172
        return BindMsg{
168✔
173
                DestPortalName:          destPortalName,
168✔
174
                PreparedStatementName:   preparedStatement,
168✔
175
                ParamVals:               params,
168✔
176
                ResultColumnFormatCodes: resultColumnFormatCodes,
168✔
177
        }, nil
168✔
178
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc