• 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.28
/pkg/pgsql/sys/pg_attribute.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 sys
18

19
import (
20
        "context"
21

22
        "github.com/codenotary/immudb/embedded/sql"
23
)
24

25
// pg_attribute: one row per column per user table. attrelid links back
26
// to pg_class via relOID("public", table.Name()) — the same scheme
27
// used on the pg_class side, so JOINs between them work.
28
//
29
// attnum is 1-based (matching PG). immudb's Column.ID() happens to be
30
// 1-based too; we use it directly so attnum order matches column-
31
// declaration order, which is what psql's \d expects.
32
func init() {
13✔
33
        sql.RegisterSystemTable(&sql.SystemTableDef{
13✔
34
                Name: "pg_attribute",
13✔
35
                Columns: []sql.SystemTableColumn{
13✔
36
                        {Name: "attrelid", Type: sql.IntegerType},
13✔
37
                        {Name: "attname", Type: sql.VarcharType, MaxLen: 128},
13✔
38
                        {Name: "atttypid", Type: sql.IntegerType},
13✔
39
                        {Name: "attstattarget", Type: sql.IntegerType},
13✔
40
                        {Name: "attlen", Type: sql.IntegerType},
13✔
41
                        {Name: "attnum", Type: sql.IntegerType},
13✔
42
                        {Name: "attndims", Type: sql.IntegerType},
13✔
43
                        {Name: "attcacheoff", Type: sql.IntegerType},
13✔
44
                        {Name: "atttypmod", Type: sql.IntegerType},
13✔
45
                        {Name: "attbyval", Type: sql.BooleanType},
13✔
46
                        {Name: "attstorage", Type: sql.VarcharType, MaxLen: 1},
13✔
47
                        {Name: "attalign", Type: sql.VarcharType, MaxLen: 1},
13✔
48
                        {Name: "attnotnull", Type: sql.BooleanType},
13✔
49
                        {Name: "atthasdef", Type: sql.BooleanType},
13✔
50
                        {Name: "atthasmissing", Type: sql.BooleanType},
13✔
51
                        {Name: "attidentity", Type: sql.VarcharType, MaxLen: 1},
13✔
52
                        {Name: "attgenerated", Type: sql.VarcharType, MaxLen: 1},
13✔
53
                        {Name: "attisdropped", Type: sql.BooleanType},
13✔
54
                        {Name: "attislocal", Type: sql.BooleanType},
13✔
55
                        {Name: "attinhcount", Type: sql.IntegerType},
13✔
56
                        {Name: "attcollation", Type: sql.IntegerType},
13✔
57
                        {Name: "attoid", Type: sql.IntegerType}, // non-standard but pgAdmin reads it
13✔
58
                },
13✔
59
                // attrelid alone isn't unique (each table has many rows) but
13✔
60
                // the registry needs a PK column — use attoid which we
13✔
61
                // synthesize per (table, column).
13✔
62
                PKColumn: "attoid",
13✔
63
                Scan: func(ctx context.Context, tx *sql.SQLTx) ([]*sql.Row, error) {
21✔
64
                        cat := tx.Catalog()
8✔
65
                        if cat == nil {
8✔
NEW
66
                                return nil, nil
×
NEW
67
                        }
×
68

69
                        tables := cat.GetTables()
8✔
70
                        // Rough pre-size: average 8 cols per table.
8✔
71
                        rows := make([]*sql.Row, 0, len(tables)*8)
8✔
72

8✔
73
                        for _, t := range tables {
16✔
74
                                relid := relOID("public", t.Name())
8✔
75

8✔
76
                                // immudb's Column.IsNullable() reports the literal
8✔
77
                                // NOT NULL flag; PostgreSQL additionally treats every
8✔
78
                                // PK member as implicitly NOT NULL. Mirror PG so psql
8✔
79
                                // \d shows the right "not null" marker.
8✔
80
                                pkCols := map[string]struct{}{}
8✔
81
                                if pk := t.PrimaryIndex(); pk != nil {
16✔
82
                                        for _, c := range pk.Cols() {
16✔
83
                                                pkCols[c.Name()] = struct{}{}
8✔
84
                                        }
8✔
85
                                }
86

87
                                for _, col := range t.Cols() {
36✔
88
                                        _, isPK := pkCols[col.Name()]
28✔
89
                                        rows = append(rows, rowPgAttribute(relid, t.Name(), col, isPK))
28✔
90
                                }
28✔
91
                        }
92
                        return rows, nil
8✔
93
                },
94
        })
95
}
96

97
func rowPgAttribute(relid int64, tableName string, col *sql.Column, isPK bool) *sql.Row {
28✔
98
        identity := ""
28✔
99
        if col.IsAutoIncremental() {
32✔
100
                identity = "d" // generated by default as identity
4✔
101
        }
4✔
102

103
        attbyval := false
28✔
104
        switch col.Type() {
28✔
105
        case sql.IntegerType, sql.BooleanType, sql.Float64Type:
16✔
106
                attbyval = true
16✔
107
        }
108

109
        // PG semantics: any PK column is NOT NULL, regardless of whether
110
        // the user wrote NOT NULL explicitly.
111
        notNull := !col.IsNullable() || isPK
28✔
112

28✔
113
        return &sql.Row{ValuesByPosition: []sql.TypedValue{
28✔
114
                sql.NewInteger(relid),
28✔
115
                sql.NewVarchar(col.Name()),
28✔
116
                sql.NewInteger(pgTypeOIDForSQLType(col.Type())),
28✔
117
                sql.NewInteger(-1), // attstattarget
28✔
118
                sql.NewInteger(pgTypeLenForSQLType(col.Type())),
28✔
119
                sql.NewInteger(int64(col.ID())),
28✔
120
                sql.NewInteger(0),  // attndims — we don't expose array types
28✔
121
                sql.NewInteger(-1), // attcacheoff
28✔
122
                sql.NewInteger(attTypmodFor(col)),
28✔
123
                sql.NewBool(attbyval),
28✔
124
                sql.NewVarchar(attStorageFor(col.Type())),
28✔
125
                sql.NewVarchar(attAlignFor(col.Type())),
28✔
126
                sql.NewBool(notNull),
28✔
127
                sql.NewBool(col.HasDefault()),
28✔
128
                sql.NewBool(false), // atthasmissing
28✔
129
                varcharOrNullIfEmpty(identity),
28✔
130
                sql.NewVarchar(""), // attgenerated — empty string = not generated
28✔
131
                sql.NewBool(false),
28✔
132
                sql.NewBool(true), // attislocal
28✔
133
                sql.NewInteger(0), // attinhcount
28✔
134
                sql.NewInteger(0), // attcollation
28✔
135
                sql.NewInteger(colOID("public", tableName, col.Name())),
28✔
136
        }}
28✔
137
}
138

139
// attTypmodFor encodes VARCHAR(N)'s N for psql's format_type(). PG's
140
// typmod encoding for varchar is `maxlen + 4`; we mirror it so
141
// format_type (once wired up) can decode cleanly.
142
func attTypmodFor(col *sql.Column) int64 {
28✔
143
        if col.Type() == sql.VarcharType && col.MaxLen() > 0 {
31✔
144
                return int64(col.MaxLen()) + 4
3✔
145
        }
3✔
146
        return -1
25✔
147
}
148

149
// attStorageFor returns the PG TOAST storage code. Fixed-width types
150
// are 'p' (plain); variable-width get 'x' (extended / compressible).
151
// Clients that don't care about TOAST ignore the column, but pgAdmin
152
// filters on it.
153
func attStorageFor(t sql.SQLValueType) string {
28✔
154
        switch t {
28✔
155
        case sql.BooleanType, sql.IntegerType, sql.Float64Type,
156
                sql.UUIDType, sql.TimestampType:
16✔
157
                return "p"
16✔
158
        default:
12✔
159
                return "x"
12✔
160
        }
161
}
162

163
// attAlignFor returns the alignment code PG stores. 'c' = char, 'i' =
164
// int (4-byte), 'd' = double (8-byte). Reasonable defaults by type.
165
func attAlignFor(t sql.SQLValueType) string {
28✔
166
        switch t {
28✔
167
        case sql.BooleanType:
7✔
168
                return "c"
7✔
169
        case sql.IntegerType, sql.Float64Type, sql.TimestampType, sql.UUIDType:
9✔
170
                return "d"
9✔
171
        default:
12✔
172
                return "i"
12✔
173
        }
174
}
175

176
func varcharOrNullIfEmpty(s string) sql.TypedValue {
28✔
177
        if s == "" {
52✔
178
                return sql.NewNull(sql.VarcharType)
24✔
179
        }
24✔
180
        return sql.NewVarchar(s)
4✔
181
}
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