712 lines
23 KiB
Go
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
|
|
}
|
|
}
|