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

712 lines
23 KiB
Go

// Copyright 2024 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 pgcatalog
import (
"fmt"
"io"
"math"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/doltgresql/core/id"
"github.com/dolthub/doltgresql/server/functions"
"github.com/dolthub/doltgresql/server/tables"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// PgConstraintName is a constant to the pg_constraint name.
const PgConstraintName = "pg_constraint"
// InitPgConstraint handles registration of the pg_constraint handler.
func InitPgConstraint() {
tables.AddHandler(PgCatalogName, PgConstraintName, PgConstraintHandler{})
}
// PgConstraintHandler is the handler for the pg_constraint table.
type PgConstraintHandler struct{}
var _ tables.Handler = PgConstraintHandler{}
var _ tables.IndexedTableHandler = PgConstraintHandler{}
// Name implements the interface tables.Handler.
func (p PgConstraintHandler) Name() string {
return PgConstraintName
}
// RowIter implements the interface tables.Handler.
func (p PgConstraintHandler) RowIter(ctx *sql.Context, partition sql.Partition) (sql.RowIter, error) {
// Use cached data from this session if it exists
pgCatalogCache, err := getPgCatalogCache(ctx)
if err != nil {
return nil, err
}
if pgCatalogCache.pgConstraints == nil {
err = cachePgConstraints(ctx, pgCatalogCache)
if err != nil {
return nil, err
}
}
if constraintIdxPart, ok := partition.(inMemIndexPartition); ok {
return &inMemIndexScanIter[*pgConstraint]{
lookup: constraintIdxPart.lookup,
rangeConverter: p,
btreeAccess: pgCatalogCache.pgConstraints,
rowConverter: pgConstraintToRow,
}, nil
}
return &pgConstraintTableScanIter{
constraintCache: pgCatalogCache.pgConstraints,
idx: 0,
}, nil
}
func getFKMatchType(matchType sql.ForeignKeyMatchType) string {
switch matchType {
case sql.ForeignKeyMatchType_Full:
return "f"
default:
return "s"
}
}
func getFKAction(action sql.ForeignKeyReferentialAction) string {
switch action {
case sql.ForeignKeyReferentialAction_NoAction:
return "a"
case sql.ForeignKeyReferentialAction_Restrict:
return "r"
case sql.ForeignKeyReferentialAction_Cascade:
return "c"
case sql.ForeignKeyReferentialAction_SetNull:
return "n"
case sql.ForeignKeyReferentialAction_SetDefault:
return "d"
default:
return ""
}
}
// PkSchema implements the interface tables.Handler.
func (p PgConstraintHandler) PkSchema() sql.PrimaryKeySchema {
return sql.PrimaryKeySchema{
Schema: PgConstraintSchema,
PkOrdinals: nil,
}
}
// Indexes implements tables.IndexedTableHandler.
func (p PgConstraintHandler) Indexes() ([]sql.Index, error) {
return []sql.Index{
pgCatalogInMemIndex{
name: "pg_constraint_oid_index",
tblName: "pg_constraint",
dbName: "pg_catalog",
uniq: true,
columnExprs: []sql.ColumnExpressionType{{Expression: "pg_constraint.oid", Type: pgtypes.Oid}},
},
pgCatalogInMemIndex{
name: "pg_constraint_conrelid_contypid_conname_index",
tblName: "pg_constraint",
dbName: "pg_catalog",
uniq: true,
columnExprs: []sql.ColumnExpressionType{
{Expression: "pg_constraint.conrelid", Type: pgtypes.Oid},
{Expression: "pg_constraint.contypid", Type: pgtypes.Oid},
{Expression: "pg_constraint.conname", Type: pgtypes.Name},
},
},
pgCatalogInMemIndex{
name: "pg_constraint_conname_nsp_index",
tblName: "pg_constraint",
dbName: "pg_catalog",
uniq: false,
columnExprs: []sql.ColumnExpressionType{
{Expression: "pg_constraint.conname", Type: pgtypes.Name},
{Expression: "pg_constraint.connamespace", Type: pgtypes.Oid},
},
},
// pg_constraint_conparentid_index is skipped because we don't support partitions, but might be worth
// implementing if it makes any tool faster
pgCatalogInMemIndex{
name: "pg_constraint_contypid_index",
tblName: "pg_constraint",
dbName: "pg_catalog",
uniq: false,
columnExprs: []sql.ColumnExpressionType{
{Expression: "pg_constraint.contypid", Type: pgtypes.Oid},
},
},
}, nil
}
// LookupPartitions implements tables.IndexedTableHandler.
func (p PgConstraintHandler) LookupPartitions(context *sql.Context, lookup sql.IndexLookup) (sql.PartitionIter, error) {
return &inMemIndexPartIter{
part: inMemIndexPartition{
idxName: lookup.Index.(pgCatalogInMemIndex).name,
lookup: lookup,
},
}, nil
}
// getIndexScanRange implements the interface RangeConverter.
func (p PgConstraintHandler) getIndexScanRange(rng sql.Range, index sql.Index) (*pgConstraint, bool, *pgConstraint, bool) {
var gte, lt *pgConstraint
var hasLowerBound, hasUpperBound bool
switch index.(pgCatalogInMemIndex).name {
case "pg_constraint_oid_index":
msrng := rng.(sql.MySQLRange)
oidRng := msrng[0]
if oidRng.HasLowerBound() {
lb := sql.GetMySQLRangeCutKey(oidRng.LowerBound)
if lb != nil {
lowerRangeCutKey := lb.(id.Id)
gte = &pgConstraint{
oidNative: idToOid(lowerRangeCutKey),
}
hasLowerBound = true
}
}
if oidRng.HasUpperBound() {
ub := sql.GetMySQLRangeCutKey(oidRng.UpperBound)
if ub != nil {
upperRangeCutKey := ub.(id.Id)
lt = &pgConstraint{
oidNative: idToOid(upperRangeCutKey) + 1,
}
hasUpperBound = true
}
}
case "pg_constraint_conrelid_contypid_conname_index":
msrng := rng.(sql.MySQLRange)
relOidRng := msrng[0]
typOidRng := msrng[1]
nameRng := msrng[2]
relOidLower := uint32(0)
relOidUpper := uint32(math.MaxUint32)
relOidUpperSet := false
typOidLower := uint32(0)
typOidUpper := uint32(math.MaxUint32)
typOidUpperSet := false
nameLower := ""
nameUpper := ""
nameUpperSet := false
if relOidRng.HasLowerBound() {
lb := sql.GetMySQLRangeCutKey(relOidRng.LowerBound)
if lb != nil {
lowerRangeCutKey := lb.(id.Id)
relOidLower = idToOid(lowerRangeCutKey)
hasLowerBound = true
}
}
if relOidRng.HasUpperBound() {
ub := sql.GetMySQLRangeCutKey(relOidRng.UpperBound)
if ub != nil {
upperRangeCutKey := ub.(id.Id)
relOidUpper = idToOid(upperRangeCutKey)
relOidUpperSet = true
hasUpperBound = true
}
}
if typOidRng.HasLowerBound() {
lb := sql.GetMySQLRangeCutKey(typOidRng.LowerBound)
if lb != nil {
lowerRangeCutKey := lb.(id.Id)
typOidLower = idToOid(lowerRangeCutKey)
hasLowerBound = true
}
}
if typOidRng.HasUpperBound() {
ub := sql.GetMySQLRangeCutKey(typOidRng.UpperBound)
if ub != nil {
upperRangeCutKey := ub.(id.Id)
typOidUpper = idToOid(upperRangeCutKey)
typOidUpperSet = true
hasUpperBound = true
}
}
if nameRng.HasLowerBound() {
lb := sql.GetMySQLRangeCutKey(nameRng.LowerBound)
if lb != nil {
nameLower = lb.(string)
hasLowerBound = true
}
}
if nameRng.HasUpperBound() {
ub := sql.GetMySQLRangeCutKey(nameRng.UpperBound)
if ub != nil {
nameUpper = ub.(string)
nameUpperSet = true
hasUpperBound = true
}
}
if hasLowerBound {
gte = &pgConstraint{
tableOidNative: relOidLower,
typeOidNative: typOidLower,
name: nameLower,
}
}
if hasUpperBound {
// our less-than upper bounds depend on how many fields were set
if typOidUpperSet {
if nameUpperSet {
nameUpper = fmt.Sprintf("%s%o", nameUpper, rune(0))
} else {
typOidUpper = typOidUpper + 1
}
} else {
if !relOidUpperSet {
relOidUpper = relOidUpper + 1
}
}
lt = &pgConstraint{
tableOidNative: relOidUpper,
typeOidNative: typOidUpper,
name: nameUpper,
}
}
case "pg_constraint_conname_nsp_index":
msrng := rng.(sql.MySQLRange)
conNameRange := msrng[0]
schemaOidRange := msrng[1]
var conNameLower, conNameUpper string
schemaOidLower := uint32(0)
schemaOidUpper := uint32(math.MaxUint32)
schemaOidUpperSet := false
if conNameRange.HasLowerBound() {
lb := sql.GetMySQLRangeCutKey(conNameRange.LowerBound)
if lb != nil {
conNameLower = lb.(string)
hasLowerBound = true
}
}
if conNameRange.HasUpperBound() {
ub := sql.GetMySQLRangeCutKey(conNameRange.UpperBound)
if ub != nil {
conNameUpper = ub.(string)
hasUpperBound = true
}
}
if schemaOidRange.HasLowerBound() {
lb := sql.GetMySQLRangeCutKey(schemaOidRange.LowerBound)
if lb != nil {
lowerRangeCutKey := lb.(id.Id)
schemaOidLower = idToOid(lowerRangeCutKey)
}
}
if schemaOidRange.HasUpperBound() {
ub := sql.GetMySQLRangeCutKey(schemaOidRange.UpperBound)
if ub != nil {
upperRangeCutKey := ub.(id.Id)
schemaOidUpper = idToOid(upperRangeCutKey)
schemaOidUpperSet = true
}
}
if conNameRange.HasLowerBound() || schemaOidRange.HasLowerBound() {
gte = &pgConstraint{
name: conNameLower,
schemaOidNative: schemaOidLower,
}
}
if conNameRange.HasUpperBound() || schemaOidRange.HasUpperBound() {
// our less-than upper bound depends on whether we have a prefix match or both fields were set
if !schemaOidUpperSet {
conNameUpper = fmt.Sprintf("%s%o", conNameUpper, rune(0))
} else {
schemaOidUpper = schemaOidUpper + 1
}
lt = &pgConstraint{
name: conNameUpper,
schemaOidNative: schemaOidUpper,
}
}
case "pg_constraint_contypid_index":
msrng := rng.(sql.MySQLRange)
typOidRng := msrng[0]
if typOidRng.HasLowerBound() {
lb := sql.GetMySQLRangeCutKey(typOidRng.LowerBound)
if lb != nil {
lowerRangeCutKey := lb.(id.Id)
gte = &pgConstraint{
typeOidNative: idToOid(lowerRangeCutKey),
}
hasLowerBound = true
}
}
if typOidRng.HasUpperBound() {
ub := sql.GetMySQLRangeCutKey(typOidRng.UpperBound)
if ub != nil {
upperRangeCutKey := ub.(id.Id)
lt = &pgConstraint{
typeOidNative: idToOid(upperRangeCutKey) + 1,
}
hasUpperBound = true
}
}
default:
panic("unknown index name: " + index.(pgCatalogInMemIndex).name)
}
return gte, hasLowerBound, lt, hasUpperBound
}
// cachePgConstraints caches the pg_constraint data for the current database in the session.
func cachePgConstraints(ctx *sql.Context, pgCatalogCache *pgCatalogCache) error {
var constraints []*pgConstraint
tableOIDs := make(map[id.Id]map[string]id.Id)
tableColToIdxMap := make(map[string]int16)
oidIdx := NewUniqueInMemIndexStorage[*pgConstraint](lessConstraintOid)
nameSchemaIdx := NewNonUniqueInMemIndexStorage[*pgConstraint](lessConstraintNameSchema)
relidTypNameIdx := NewUniqueInMemIndexStorage[*pgConstraint](lessConstraintRelidTypeName)
typIdx := NewNonUniqueInMemIndexStorage[*pgConstraint](lessConstraintType)
// We iterate over all tables first to obtain their OIDs, which we'll need to reference for foreign keys
err := functions.IterateCurrentDatabase(ctx, functions.Callbacks{
Table: func(ctx *sql.Context, schema functions.ItemSchema, table functions.ItemTable) (cont bool, err error) {
inner, ok := tableOIDs[schema.OID.AsId()]
if !ok {
inner = make(map[string]id.Id)
tableOIDs[schema.OID.AsId()] = inner
}
inner[table.Item.Name()] = table.OID.AsId()
for i, col := range table.Item.Schema(ctx) {
tableColToIdxMap[fmt.Sprintf("%s.%s", table.Item.Name(), col.Name)] = int16(i + 1)
}
return true, nil
},
})
if err != nil {
return err
}
// Then we iterate over everything to fill our constraints
err = functions.IterateCurrentDatabase(ctx, functions.Callbacks{
Check: func(ctx *sql.Context, schema functions.ItemSchema, table functions.ItemTable, check functions.ItemCheck) (cont bool, err error) {
constraint := &pgConstraint{
oid: check.OID.AsId(),
oidNative: id.Cache().ToOID(check.OID.AsId()),
name: check.Item.Name,
schemaOid: schema.OID.AsId(),
schemaOidNative: id.Cache().ToOID(schema.OID.AsId()),
conType: "c",
tableOid: table.OID.AsId(),
tableOidNative: id.Cache().ToOID(table.OID.AsId()),
typeOid: id.Id(id.NewOID(0)),
convalidated: !check.Item.IsNotValid,
}
oidIdx.Add(constraint)
relidTypNameIdx.Add(constraint)
nameSchemaIdx.Add(constraint)
typIdx.Add(constraint)
constraints = append(constraints, constraint)
return true, nil
},
ForeignKey: func(ctx *sql.Context, schema functions.ItemSchema, table functions.ItemTable, foreignKey functions.ItemForeignKey) (cont bool, err error) {
conKey := make([]any, len(foreignKey.Item.Columns))
for i, col := range foreignKey.Item.Columns {
keyExpr := fmt.Sprintf("%s.%s", foreignKey.Item.Table, col)
conKey[i] = tableColToIdxMap[keyExpr]
}
parentTableColToIdxMap := make(map[string]int16)
parentTable, ok, err := schema.Item.GetTableInsensitive(ctx, foreignKey.Item.ParentTable)
if err != nil {
return false, err
} else if ok {
for i, col := range parentTable.Schema(ctx) {
parentTableColToIdxMap[col.Name] = int16(i + 1)
}
}
conFkey := make([]any, len(foreignKey.Item.ParentColumns))
for i, expr := range foreignKey.Item.ParentColumns {
conFkey[i] = parentTableColToIdxMap[expr]
}
constraint := &pgConstraint{
oid: foreignKey.OID.AsId(),
oidNative: id.Cache().ToOID(foreignKey.OID.AsId()),
name: foreignKey.Item.Name,
schemaOid: schema.OID.AsId(),
schemaOidNative: id.Cache().ToOID(schema.OID.AsId()),
conType: "f",
tableOid: tableOIDs[schema.OID.AsId()][foreignKey.Item.Table],
tableOidNative: id.Cache().ToOID(tableOIDs[schema.OID.AsId()][foreignKey.Item.Table]),
idxOid: foreignKey.OID.AsId(),
tableRefOid: tableOIDs[schema.OID.AsId()][foreignKey.Item.ParentTable],
fkUpdateType: getFKAction(foreignKey.Item.OnUpdate),
fkDeleteType: getFKAction(foreignKey.Item.OnDelete),
fkMatchType: getFKMatchType(foreignKey.Item.MatchType),
conKey: conKey,
conFkey: conFkey,
typeOid: id.Id(id.NewOID(0)),
convalidated: !foreignKey.Item.IsNotValid,
}
oidIdx.Add(constraint)
relidTypNameIdx.Add(constraint)
nameSchemaIdx.Add(constraint)
typIdx.Add(constraint)
constraints = append(constraints, constraint)
return true, nil
},
Index: func(ctx *sql.Context, schema functions.ItemSchema, table functions.ItemTable, index functions.ItemIndex) (cont bool, err error) {
conType := "p"
if index.Item.ID() != "PRIMARY" {
if index.Item.IsUnique() {
conType = "u"
} else {
// If this isn't a primary key or a unique index, then it's a regular index, and not
// a constraint, so we don't need to report it in the pg_constraint table.
return true, nil
}
}
conKey := make([]any, len(index.Item.Expressions()))
for i, expr := range index.Item.Expressions() {
conKey[i] = tableColToIdxMap[expr]
}
constraint := &pgConstraint{
oid: index.OID.AsId(),
oidNative: id.Cache().ToOID(index.OID.AsId()),
name: formatIndexName(index.Item),
schemaOid: schema.OID.AsId(),
schemaOidNative: id.Cache().ToOID(schema.OID.AsId()),
conType: conType,
tableOid: table.OID.AsId(),
tableOidNative: id.Cache().ToOID(table.OID.AsId()),
idxOid: index.OID.AsId(),
conKey: conKey,
typeOid: id.Id(id.NewOID(0)),
convalidated: true,
}
oidIdx.Add(constraint)
relidTypNameIdx.Add(constraint)
nameSchemaIdx.Add(constraint)
typIdx.Add(constraint)
constraints = append(constraints, constraint)
return true, nil
},
Type: func(ctx *sql.Context, schema functions.ItemSchema, typ functions.ItemType) (cont bool, err error) {
for _, check := range typ.Item.Checks {
checkOid := id.NewCheck(schema.Item.SchemaName(), "", check.Name)
constraint := &pgConstraint{
oid: checkOid.AsId(),
oidNative: id.Cache().ToOID(checkOid.AsId()),
name: check.Name,
schemaOid: schema.OID.AsId(),
schemaOidNative: id.Cache().ToOID(schema.OID.AsId()),
conType: "c",
typeOid: typ.Oid.AsId(),
typeOidNative: id.Cache().ToOID(typ.Oid.AsId()),
}
oidIdx.Add(constraint)
relidTypNameIdx.Add(constraint)
nameSchemaIdx.Add(constraint)
typIdx.Add(constraint)
constraints = append(constraints, constraint)
}
return true, nil
},
})
if err != nil {
return err
}
pgCatalogCache.pgConstraints = &pgConstraintCache{
constraints: constraints,
oidIdx: oidIdx,
relidTypNameIdx: relidTypNameIdx,
nameSchemaIdx: nameSchemaIdx,
typIdx: typIdx,
}
return nil
}
// lessConstraintOid is a sort function for pgConstraint based on oid.
func lessConstraintOid(a, b *pgConstraint) bool {
return a.oidNative < b.oidNative
}
// lessConstraintRelidTypeName is a sort function for pgConstraint based on conrelid, contypid, then conname.
func lessConstraintRelidTypeName(a, b *pgConstraint) bool {
if a.tableOidNative == b.tableOidNative {
if a.typeOidNative == b.typeOidNative {
return a.name < b.name
}
return a.typeOidNative < b.typeOidNative
}
return a.tableOidNative < b.tableOidNative
}
// lessConstraintNameSchema is a sort function for pgConstraint based on conname, then schemaOid.
func lessConstraintNameSchema(a, b []*pgConstraint) bool {
if a[0].name == b[0].name {
return a[0].schemaOidNative < b[0].schemaOidNative
}
return a[0].name < b[0].name
}
// lessConstraintType is a sort function for pgConstraint based on type oid.
func lessConstraintType(a, b []*pgConstraint) bool {
return a[0].typeOidNative < b[0].typeOidNative
}
// PgConstraintSchema is the schema for pg_constraint.
var PgConstraintSchema = sql.Schema{
{Name: "oid", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: PgConstraintName},
{Name: "conname", Type: pgtypes.Name, Default: nil, Nullable: false, Source: PgConstraintName},
{Name: "connamespace", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: PgConstraintName},
{Name: "contype", Type: pgtypes.InternalChar, Default: nil, Nullable: false, Source: PgConstraintName},
{Name: "condeferrable", Type: pgtypes.Bool, Default: nil, Nullable: false, Source: PgConstraintName},
{Name: "condeferred", Type: pgtypes.Bool, Default: nil, Nullable: false, Source: PgConstraintName},
{Name: "convalidated", Type: pgtypes.Bool, Default: nil, Nullable: false, Source: PgConstraintName},
{Name: "conrelid", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: PgConstraintName},
{Name: "contypid", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: PgConstraintName},
{Name: "conindid", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: PgConstraintName},
{Name: "conparentid", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: PgConstraintName},
{Name: "confrelid", Type: pgtypes.Oid, Default: nil, Nullable: false, Source: PgConstraintName},
{Name: "confupdtype", Type: pgtypes.InternalChar, Default: nil, Nullable: false, Source: PgConstraintName},
{Name: "confdeltype", Type: pgtypes.InternalChar, Default: nil, Nullable: false, Source: PgConstraintName},
{Name: "confmatchtype", Type: pgtypes.InternalChar, Default: nil, Nullable: false, Source: PgConstraintName},
{Name: "conislocal", Type: pgtypes.Bool, Default: nil, Nullable: false, Source: PgConstraintName},
{Name: "coninhcount", Type: pgtypes.Int16, Default: nil, Nullable: false, Source: PgConstraintName},
{Name: "connoinherit", Type: pgtypes.Bool, Default: nil, Nullable: false, Source: PgConstraintName},
{Name: "conkey", Type: pgtypes.Int16Array, Default: nil, Nullable: true, Source: PgConstraintName},
{Name: "confkey", Type: pgtypes.Int16Array, Default: nil, Nullable: true, Source: PgConstraintName},
{Name: "conpfeqop", Type: pgtypes.OidArray, Default: nil, Nullable: true, Source: PgConstraintName},
{Name: "conppeqop", Type: pgtypes.OidArray, Default: nil, Nullable: true, Source: PgConstraintName},
{Name: "conffeqop", Type: pgtypes.OidArray, Default: nil, Nullable: true, Source: PgConstraintName},
{Name: "confdelsetcols", Type: pgtypes.Int16Array, Default: nil, Nullable: true, Source: PgConstraintName},
{Name: "conexclop", Type: pgtypes.OidArray, Default: nil, Nullable: true, Source: PgConstraintName},
{Name: "conbin", Type: pgtypes.Text, Default: nil, Nullable: true, Source: PgConstraintName}, // TODO: type pg_node_tree, collation C
}
// pgConstraint is the struct for the pg_constraint table.
// We store oids in their native format as well so that we can do range scans on them.
type pgConstraint struct {
oid id.Id
oidNative uint32
name string
schemaOid id.Id
schemaOidNative uint32
conType string // c = check constraint, f = foreign key constraint, p = primary key constraint, u = unique constraint, t = constraint trigger, x = exclusion constraint
tableOid id.Id
tableOidNative uint32
typeOid id.Id
typeOidNative uint32
idxOid id.Id
tableRefOid id.Id
fkUpdateType string // a = no action, r = restrict, c = cascade, n = set null, d = set default
fkDeleteType string // a = no action, r = restrict, c = cascade, n = set null, d = set default
fkMatchType string // f = full, p = partial, s = simple
conKey []any
conFkey []any
convalidated bool
}
// pgConstraintTableScanIter is the sql.RowIter for the pg_constraint table.
type pgConstraintTableScanIter struct {
constraintCache *pgConstraintCache
idx int
}
var _ sql.RowIter = (*pgConstraintTableScanIter)(nil)
// Next implements the interface sql.RowIter.
func (iter *pgConstraintTableScanIter) Next(ctx *sql.Context) (sql.Row, error) {
if iter.idx >= len(iter.constraintCache.constraints) {
return nil, io.EOF
}
iter.idx++
constraint := iter.constraintCache.constraints[iter.idx-1]
return pgConstraintToRow(constraint), nil
}
// Close implements the interface sql.RowIter.
func (iter *pgConstraintTableScanIter) Close(ctx *sql.Context) error {
return nil
}
// pgConstraintToRow converts a pgConstraint to a sql.Row.
func pgConstraintToRow(constraint *pgConstraint) sql.Row {
var conKey interface{}
if len(constraint.conKey) == 0 {
conKey = nil
} else {
conKey = constraint.conKey
}
var conFkey interface{}
if len(constraint.conFkey) == 0 {
conFkey = nil
} else {
conFkey = constraint.conFkey
}
return sql.Row{
constraint.oid, // oid
constraint.name, // conname
constraint.schemaOid, // connamespace
constraint.conType, // contype
false, // condeferrable
false, // condeferred
constraint.convalidated, // convalidated
constraint.tableOid, // conrelid
constraint.typeOid, // contypid
constraint.idxOid, // conindid
id.Null, // conparentid
constraint.tableRefOid, // confrelid
constraint.fkUpdateType, // confupdtype
constraint.fkDeleteType, // confdeltype
constraint.fkMatchType, // confmatchtype
true, // conislocal
int16(0), // coninhcount
true, // connoinherit
conKey, // conkey
conFkey, // confkey
nil, // conpfeqop
nil, // conppeqop
nil, // conffeqop
nil, // confdelsetcols
nil, // conexclop
nil, // conbin
}
}