blob: 5c1f0e86bc079eca14f284e3d04b41fd506f11f9 [file]
package backup
/*
* This file contains structs and functions related to backing up query planner
* statistics on the coordinator.
*/
import (
"fmt"
"strings"
"github.com/apache/cloudberry-backup/toc"
"github.com/apache/cloudberry-backup/utils"
"github.com/lib/pq"
)
const (
// STATISTIC_KIND_NDV_BY_SEGMENTS is specific to Cloudberry Database 2.1.0+
STATISTIC_KIND_NDV_BY_SEGMENTS = 8
)
func PrintStatisticsStatements(statisticsFile *utils.FileWithByteCount, tocfile *toc.TOC, tables []Table, attStats map[uint32][]AttributeStatistic, tupleStats map[uint32]TupleStatistic) {
for _, table := range tables {
tupleQuery := GenerateTupleStatisticsQuery(table, tupleStats[table.Oid])
printStatisticsStatementForTable(statisticsFile, tocfile, table, tupleQuery)
for _, attStat := range attStats[table.Oid] {
attributeQueries := GenerateAttributeStatisticsQueries(table, attStat)
for _, attrQuery := range attributeQueries {
printStatisticsStatementForTable(statisticsFile, tocfile, table, attrQuery)
}
}
}
}
func printStatisticsStatementForTable(statisticsFile *utils.FileWithByteCount, tocfile *toc.TOC, table Table, query string) {
start := statisticsFile.ByteCount
statisticsFile.MustPrintf("\n\n%s\n", query)
entry := toc.MetadataEntry{Schema: table.Schema, Name: table.Name, ObjectType: toc.OBJ_STATISTICS}
tocfile.AddMetadataEntry("statistics", entry, start, statisticsFile.ByteCount, []uint32{0, 0})
}
func GenerateTupleStatisticsQuery(table Table, tupleStat TupleStatistic) string {
tupleQuery := `UPDATE pg_class
SET
relpages = %d::int,
reltuples = %f::real
WHERE oid = '%s'::regclass::oid;`
return fmt.Sprintf(
tupleQuery,
tupleStat.RelPages,
tupleStat.RelTuples,
utils.EscapeSingleQuotes(table.FQN()))
}
func GenerateAttributeStatisticsQueries(table Table, attStat AttributeStatistic) []string {
/*
* When restoring statistics to a new database, we cannot determine what the
* new OID for a given object will be, so we need to perform an explicit cast
* from the name to an OID in the below statements, rather than backing up the
* OID in the source database.
*/
starelidStr := fmt.Sprintf("'%s'::regclass::oid", utils.EscapeSingleQuotes(table.FQN()))
// The entry may or may not already exist, so we can't either just UPDATE or just INSERT without a DELETE.
inheritStr := ""
attributeSlotsQueryStr := ""
var attributeQueries []string
if (connectionPool.Version.IsGPDB() && connectionPool.Version.AtLeast("6")) || connectionPool.Version.IsCBDB() {
inheritStr = fmt.Sprintf("\n\t%t::boolean,", attStat.Inherit)
if (connectionPool.Version.IsGPDB() && connectionPool.Version.AtLeast("7")) || connectionPool.Version.IsCBDB() {
attributeSlotsQueryStr = generateAttributeSlotsQuery7(attStat)
} else {
attributeSlotsQueryStr = generateAttributeSlotsQuery6(attStat)
}
} else {
attributeSlotsQueryStr = generateAttributeSlotsQuery4(attStat)
}
attributeQueries = append(attributeQueries, fmt.Sprintf(`DELETE FROM pg_statistic WHERE starelid = %s AND staattnum = %d;`, starelidStr, attStat.AttNumber))
attributeQueries = append(attributeQueries, fmt.Sprintf(`INSERT INTO pg_statistic VALUES (
%s,
%d::smallint,%s
%f::real,
%d::integer,
%f::real,
%s);`, starelidStr, attStat.AttNumber, inheritStr, attStat.NullFraction, attStat.Width, attStat.Distinct, attributeSlotsQueryStr))
/*
* If a type name starts with exactly one underscore, it describes an array
* type. We can't restore statistics of array columns, so we'll zero and
* NULL everything out.
*/
return attributeQueries
}
// GPDB7 introduced statistic collations
func generateAttributeSlotsQuery7(attStat AttributeStatistic) string {
attributeQuery := ""
if len(attStat.Type) > 1 && attStat.Type[0] == '_' && attStat.Type[1] != '_' {
attributeQuery = `0::smallint,
0::smallint,
0::smallint,
0::smallint,
0::smallint,
0::oid,
0::oid,
0::oid,
0::oid,
0::oid,
0::oid,
0::oid,
0::oid,
0::oid,
0::oid,
NULL::real[],
NULL::real[],
NULL::real[],
NULL::real[],
NULL::real[],
NULL,
NULL,
NULL,
NULL,
NULL`
} else {
attributeQuery = fmt.Sprintf(`%d::smallint,
%d::smallint,
%d::smallint,
%d::smallint,
%d::smallint,
%d::oid,
%d::oid,
%d::oid,
%d::oid,
%d::oid,
%d::oid,
%d::oid,
%d::oid,
%d::oid,
%d::oid,
%s::real[],
%s::real[],
%s::real[],
%s::real[],
%s::real[],
%s,
%s,
%s,
%s,
%s`, attStat.Kind1,
attStat.Kind2,
attStat.Kind3,
attStat.Kind4,
attStat.Kind5,
attStat.Operator1,
attStat.Operator2,
attStat.Operator3,
attStat.Operator4,
attStat.Operator5,
attStat.Collation1,
attStat.Collation2,
attStat.Collation3,
attStat.Collation4,
attStat.Collation5,
realValues(attStat.Numbers1),
realValues(attStat.Numbers2),
realValues(attStat.Numbers3),
realValues(attStat.Numbers4),
realValues(attStat.Numbers5),
AnyValues(attStat.Values1, attStat.Type, attStat.Kind1),
AnyValues(attStat.Values2, attStat.Type, attStat.Kind2),
AnyValues(attStat.Values3, attStat.Type, attStat.Kind3),
AnyValues(attStat.Values4, attStat.Type, attStat.Kind4),
AnyValues(attStat.Values5, attStat.Type, attStat.Kind5))
}
return attributeQuery
}
// GPDB6 introduced an additional statistic slot that we account for in this function
func generateAttributeSlotsQuery6(attStat AttributeStatistic) string {
attributeQuery := ""
if len(attStat.Type) > 1 && attStat.Type[0] == '_' && attStat.Type[1] != '_' {
attributeQuery = `0::smallint,
0::smallint,
0::smallint,
0::smallint,
0::smallint,
0::oid,
0::oid,
0::oid,
0::oid,
0::oid,
NULL::real[],
NULL::real[],
NULL::real[],
NULL::real[],
NULL::real[],
NULL,
NULL,
NULL,
NULL,
NULL`
} else {
attributeQuery = fmt.Sprintf(`%d::smallint,
%d::smallint,
%d::smallint,
%d::smallint,
%d::smallint,
%d::oid,
%d::oid,
%d::oid,
%d::oid,
%d::oid,
%s::real[],
%s::real[],
%s::real[],
%s::real[],
%s::real[],
%s,
%s,
%s,
%s,
%s`, attStat.Kind1,
attStat.Kind2,
attStat.Kind3,
attStat.Kind4,
attStat.Kind5,
attStat.Operator1,
attStat.Operator2,
attStat.Operator3,
attStat.Operator4,
attStat.Operator5,
realValues(attStat.Numbers1),
realValues(attStat.Numbers2),
realValues(attStat.Numbers3),
realValues(attStat.Numbers4),
realValues(attStat.Numbers5),
AnyValues(attStat.Values1, attStat.Type, attStat.Kind1),
AnyValues(attStat.Values2, attStat.Type, attStat.Kind2),
AnyValues(attStat.Values3, attStat.Type, attStat.Kind3),
AnyValues(attStat.Values4, attStat.Type, attStat.Kind4),
AnyValues(attStat.Values5, attStat.Type, attStat.Kind5))
}
return attributeQuery
}
func generateAttributeSlotsQuery4(attStat AttributeStatistic) string {
attributeQuery := ""
if len(attStat.Type) > 1 && attStat.Type[0] == '_' && attStat.Type[1] != '_' {
attributeQuery = `0::smallint,
0::smallint,
0::smallint,
0::smallint,
0::oid,
0::oid,
0::oid,
0::oid,
NULL::real[],
NULL::real[],
NULL::real[],
NULL::real[],
NULL,
NULL,
NULL,
NULL`
} else {
attributeQuery = fmt.Sprintf(`%d::smallint,
%d::smallint,
%d::smallint,
%d::smallint,
%d::oid,
%d::oid,
%d::oid,
%d::oid,
%s::real[],
%s::real[],
%s::real[],
%s::real[],
%s,
%s,
%s,
%s`, attStat.Kind1,
attStat.Kind2,
attStat.Kind3,
attStat.Kind4,
attStat.Operator1,
attStat.Operator2,
attStat.Operator3,
attStat.Operator4,
realValues(attStat.Numbers1),
realValues(attStat.Numbers2),
realValues(attStat.Numbers3),
realValues(attStat.Numbers4),
AnyValues(attStat.Values1, attStat.Type, attStat.Kind1),
AnyValues(attStat.Values2, attStat.Type, attStat.Kind2),
AnyValues(attStat.Values3, attStat.Type, attStat.Kind3),
AnyValues(attStat.Values4, attStat.Type, attStat.Kind4))
}
return attributeQuery
}
// It is assumed that the elements in the input slice are already escaped
func SliceToPostgresArray(slice []string) string {
quotedStrings := make([]string, len(slice))
for i, str := range slice {
// Escape single quotes because we are using array_in
escapedStr := utils.EscapeSingleQuotes(str)
// Store a Go-syntax representation of the value because writing to the
// file will evaluate the string
quotedStrings[i] = fmt.Sprintf(`%#v`, escapedStr)
}
return fmt.Sprintf(`'{%s}'`, strings.Join(quotedStrings, ","))
}
func realValues(reals pq.StringArray) string {
if len(reals) > 0 {
return SliceToPostgresArray(reals)
}
return "NULL"
}
/*
* A given type is not guaranteed to have a corresponding array type, so we need
* to use array_in() instead of casting to an array.
* STATISTIC_KIND_NDV_BY_SEGMENTS (8) is a special case which stores an array of
* int8 values rather than the column's native type.
*/
func AnyValues(any pq.StringArray, typ string, kind int) string {
if len(any) > 0 {
if kind == STATISTIC_KIND_NDV_BY_SEGMENTS {
return fmt.Sprintf(`array_in(%s, 'int8'::regtype::oid, -1)`, SliceToPostgresArray(any))
}
return fmt.Sprintf(`array_in(%s, '%s'::regtype::oid, -1)`, SliceToPostgresArray(any), typ)
}
return "NULL"
}