From 2643eea7633cdbb7916d19cbde6a2da5e1cb9906 Mon Sep 17 00:00:00 2001 From: Yilin Date: Tue, 29 Sep 2026 00:25:26 +0800 Subject: [PATCH 01/18] feat: add user attributes view --- kwbase/pkg/sql/information_schema.go | 33 ++++++++++ .../testdata/logic_test/user_attributes | 64 +++++++++++++++++++ kwbase/pkg/sql/sqlbase/constants.go | 3 +- 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 kwbase/pkg/sql/logictest/testdata/logic_test/user_attributes diff --git a/kwbase/pkg/sql/information_schema.go b/kwbase/pkg/sql/information_schema.go index 02595d21c9..ede3a06a0d 100644 --- a/kwbase/pkg/sql/information_schema.go +++ b/kwbase/pkg/sql/information_schema.go @@ -127,6 +127,7 @@ var informationSchema = virtualSchema{ "triggers", "udt_privileges", "usage_privileges", + "user_attributes", "user_defined_types", "user_mapping_options", "user_mappings", @@ -159,6 +160,7 @@ var informationSchema = virtualSchema{ sqlbase.InformationSchemaViewsTableID: informationSchemaViewsTable, sqlbase.InformationSchemaUserPrivilegesID: informationSchemaUserPrivileges, sqlbase.InformationSchemaProcedurePrivilegesID: informationSchemaProcedurePrivileges, + sqlbase.InformationSchemaUserAttributesTableID: informationSchemaUserAttributes, }, tableValidator: validateInformationSchemaTable, validWithNoDatabaseContext: true, @@ -1387,6 +1389,37 @@ CREATE TABLE information_schema.user_privileges ( }, } +// MySQL: https://dev.mysql.com/doc/refman/8.0/en/information-schema-user-attributes-table.html +var informationSchemaUserAttributes = virtualSchemaTable{ + comment: `user account attributes (partial MySQL compatibility)`, + schema: ` +CREATE TABLE information_schema.user_attributes ( + USER STRING NOT NULL, + HOST STRING, + ATTRIBUTE JSONB +)`, + populate: func(ctx context.Context, p *planner, _ *DatabaseDescriptor, addRow func(...tree.Datum) error) error { + currentUser := p.SessionData().User + isAdmin, err := p.HasAdminRole(ctx) + if err != nil { + return err + } + + return forEachRole(ctx, p, func(username string, isRole bool, _ bool) error { + // User attributes apply to login accounts, not SQL roles. Non-admins + // are restricted to their own row to avoid account enumeration. + if isRole || (!isAdmin && username != currentUser) { + return nil + } + return addRow( + tree.NewDString(username), // user + tree.DNull, // host: KWDB has no user@host identity + tree.DNull, // attribute: no account JSON attribute source + ) + }) + }, +} + // MySQL: https://dev.mysql.com/doc/refman/5.7/en/table-privileges-table.html var informationSchemaTablePrivileges = virtualSchemaTable{ comment: `privileges granted on table or views (incomplete; may contain excess users or roles) diff --git a/kwbase/pkg/sql/logictest/testdata/logic_test/user_attributes b/kwbase/pkg/sql/logictest/testdata/logic_test/user_attributes new file mode 100644 index 0000000000..d067add6e5 --- /dev/null +++ b/kwbase/pkg/sql/logictest/testdata/logic_test/user_attributes @@ -0,0 +1,64 @@ +# LogicTest: local + +# information_schema.user_attributes exposes account metadata without allowing +# non-admin users to enumerate other accounts. +statement ok +CREATE USER ua_self + +statement ok +CREATE USER ua_other + +statement ok +CREATE USER ua_admin_member + +statement ok +CREATE ROLE ua_role + +statement ok +GRANT admin TO ua_admin_member + +query TTBTTTBB colnames +SHOW COLUMNS FROM information_schema.user_attributes +---- +column_name data_type is_nullable column_default generation_expression indices is_hidden is_tag +user STRING false NULL · {} false false +host STRING true NULL · {} false false +attribute JSONB true NULL · {} false false + +# Root sees all account rows but no SQL role row. HOST and ATTRIBUTE remain +# SQL NULL because KWDB does not store MySQL user@host or JSON attributes. +user root + +query TBB +SELECT "user", host IS NULL, attribute IS NULL + FROM information_schema.user_attributes + WHERE "user" IN ('ua_self', 'ua_other', 'ua_admin_member', 'ua_role') + ORDER BY "user" +---- +ua_admin_member true true +ua_other true true +ua_self true true + +# A normal account can inspect only its own row. +user ua_self + +query TBB +SELECT "user", host IS NULL, attribute IS NULL + FROM information_schema.user_attributes + WHERE "user" IN ('ua_self', 'ua_other', 'ua_admin_member', 'ua_role') + ORDER BY "user" +---- +ua_self true true + +# Membership in admin grants the same complete account view as root. +user ua_admin_member + +query TBB +SELECT "user", host IS NULL, attribute IS NULL + FROM information_schema.user_attributes + WHERE "user" IN ('ua_self', 'ua_other', 'ua_admin_member', 'ua_role') + ORDER BY "user" +---- +ua_admin_member true true +ua_other true true +ua_self true true diff --git a/kwbase/pkg/sql/sqlbase/constants.go b/kwbase/pkg/sql/sqlbase/constants.go index 53ec1f3f36..dd42dba7bc 100644 --- a/kwbase/pkg/sql/sqlbase/constants.go +++ b/kwbase/pkg/sql/sqlbase/constants.go @@ -248,7 +248,8 @@ const ( KwdbInternalKWDBPublicationsTableID CrdbInternalTSTransactionRecordID InformationSchemaKeywordsTableID - MinVirtualID = InformationSchemaKeywordsTableID + InformationSchemaUserAttributesTableID + MinVirtualID = InformationSchemaUserAttributesTableID ) const ( -- Gitee From 2c03a52e80c06b5f6bf0582ad64bb87d24795d02 Mon Sep 17 00:00:00 2001 From: Yilin Date: Tue, 29 Sep 2026 21:13:14 +0800 Subject: [PATCH 02/18] sql: extend internal partitions metadata --- kwbase/pkg/sql/kwdb_internal.go | 546 +++++++++++------- kwbase/pkg/sql/kwdb_internal_test.go | 71 --- .../testdata/logic_test/kwdb_internal | 92 +++ 3 files changed, 431 insertions(+), 278 deletions(-) diff --git a/kwbase/pkg/sql/kwdb_internal.go b/kwbase/pkg/sql/kwdb_internal.go index b4ebd135e3..56b5abef54 100644 --- a/kwbase/pkg/sql/kwdb_internal.go +++ b/kwbase/pkg/sql/kwdb_internal.go @@ -3166,236 +3166,368 @@ CREATE TABLE kwdb_internal.gossip_network ( }, } -// addPartitioningRows adds the rows in kwdb_internal.partitions for each partition. -// None of the arguments can be nil, and it is used recursively when a list partition -// has subpartitions. In that case, the colOffset argument is incremented to represent -// how many columns of the index have been partitioned already. -func addPartitioningRows( - ctx context.Context, - p *planner, - database string, - table *sqlbase.TableDescriptor, - index *sqlbase.IndexDescriptor, - partitioning *sqlbase.PartitioningDescriptor, - parentName tree.Datum, - colOffset int, - addRow func(...tree.Datum) error, -) error { - tableID := tree.NewDInt(tree.DInt(table.ID)) - indexID := tree.NewDInt(tree.DInt(index.ID)) - numColumns := tree.NewDInt(tree.DInt(partitioning.NumColumns)) +var kwdbInternalPartitionsTable = virtualSchemaTable{ + comment: "defined partitions for all tables/indexes accessible by the current user in the current database (KV scan)", + schema: ` +CREATE TABLE kwdb_internal.partitions ( + table_id INT8 NOT NULL, + index_id INT8 NOT NULL, + parent_name STRING, + name STRING NOT NULL, + columns INT8 NOT NULL, + column_names STRING, + list_value STRING, + range_value STRING, + hash_value STRING, + zone_id INT8, -- references a zone id in the kwdb_internal.zones table + subzone_id INT8, -- references a subzone id in the kwdb_internal.zones table + table_catalog STRING, + table_schema STRING, + table_name STRING, + partition_name STRING, + subpartition_name STRING, + partition_ordinal_position STRING, + subpartition_ordinal_position STRING, + partition_method STRING, + subpartition_method STRING, + partition_expression STRING, + subpartition_expression STRING, + partition_description STRING, + table_rows INT8, + avg_row_length INT8, + data_length INT8, + max_data_length INT8, + index_length INT8, + data_free INT8, + create_time TIMESTAMP, + update_time TIMESTAMP, + check_time TIMESTAMP, + checksum STRING, + partition_comment STRING, + nodegroup STRING, + tablespace_name STRING +) + `, + populate: func(ctx context.Context, p *planner, dbContext *DatabaseDescriptor, addRow func(...tree.Datum) error) error { - var buf bytes.Buffer - for i := uint32(colOffset); i < uint32(colOffset)+partitioning.NumColumns; i++ { - if i != uint32(colOffset) { - buf.WriteString(`, `) - } - buf.WriteString(index.ColumnNames[i]) - } - colNames := tree.NewDString(buf.String()) + // name tracks the immediate parent for the legacy parent_name column. The + // root fields preserve the top-level partition metadata for MySQL-compatible + // PARTITION_* columns when recursively visiting deeper subpartitions. + type partitionParentInfo struct { + isSubpartition bool + name tree.Datum + rootName tree.Datum + rootOrdinal tree.Datum + rootMethod tree.Datum + rootExpression tree.Datum + } + + var addPartitioningRowsWithContext func( + ctx context.Context, + p *planner, + database string, + schema string, + table *sqlbase.TableDescriptor, + index *sqlbase.IndexDescriptor, + partitioning *sqlbase.PartitioningDescriptor, + parent partitionParentInfo, + colOffset int, + addRow func(...tree.Datum) error, + ) error + addPartitioningRowsWithContext = func( + ctx context.Context, + p *planner, + database string, + schema string, + table *sqlbase.TableDescriptor, + index *sqlbase.IndexDescriptor, + partitioning *sqlbase.PartitioningDescriptor, + parent partitionParentInfo, + colOffset int, + addRow func(...tree.Datum) error, + ) error { + tableID := tree.NewDInt(tree.DInt(table.ID)) + indexID := tree.NewDInt(tree.DInt(index.ID)) + numColumns := tree.NewDInt(tree.DInt(partitioning.NumColumns)) + var colNameBuf bytes.Buffer + for i := uint32(colOffset); i < uint32(colOffset)+partitioning.NumColumns; i++ { + if i != uint32(colOffset) { + colNameBuf.WriteString(`, `) + } + colNameBuf.WriteString(index.ColumnNames[i]) + } + colNames := tree.NewDString(colNameBuf.String()) + + method := tree.DNull + if partitioning.IsHash || len(partitioning.HashPoint) > 0 { + method = tree.NewDString("HASH") + } else if len(partitioning.List) > 0 { + method = tree.NewDString("LIST") + } else if len(partitioning.Range) > 0 { + method = tree.NewDString("RANGE") + } + + var datumAlloc sqlbase.DatumAlloc + + // We don't need real prefixes in the DecodePartitionTuple calls because we + // only use the tree.Datums part of the output. + fakePrefixDatums := make([]tree.Datum, colOffset) + for i := range fakePrefixDatums { + fakePrefixDatums[i] = tree.DNull + } + + ordinal := 0 + addPartitionRow := func( + name *tree.DString, + listValue tree.Datum, + rangeValue tree.Datum, + hashValue tree.Datum, + description tree.Datum, + zoneID uint32, + subzoneID base.SubzoneID, + ) error { + ordinal++ + ordinalDatum := tree.NewDString(strconv.Itoa(ordinal)) + + var partitionName tree.Datum = name + var subpartitionName tree.Datum = tree.DNull + var partitionOrdinal tree.Datum = ordinalDatum + var subpartitionOrdinal tree.Datum = tree.DNull + partitionMethodDatum := method + var subpartitionMethod tree.Datum = tree.DNull + var partitionExpr tree.Datum = colNames + var subpartitionExpr tree.Datum = tree.DNull + + // MySQL exposes a fixed partition/subpartition pair. For every nested + // node, keep PARTITION_* anchored at the top level and expose the current + // node through SUBPARTITION_*; legacy name/parent_name retains each edge. + if parent.isSubpartition { + partitionName = parent.rootName + subpartitionName = name + partitionOrdinal = parent.rootOrdinal + subpartitionOrdinal = ordinalDatum + partitionMethodDatum = parent.rootMethod + subpartitionMethod = method + partitionExpr = parent.rootExpression + subpartitionExpr = colNames + } - var datumAlloc sqlbase.DatumAlloc + // The already decoded LIST, RANGE, or HASH value is also the most + // accurate available description of this partition's boundary. + return addRow( + tableID, // table_id + indexID, // index_id + parent.name, // parent_name + name, // name + numColumns, // columns + colNames, // column_names + listValue, // list_value + rangeValue, // range_value + hashValue, // hash_value + tree.NewDInt(tree.DInt(zoneID)), // zone_id + tree.NewDInt(tree.DInt(subzoneID)), // subzone_id + tree.NewDString(database), // table_catalog + tree.NewDString(schema), // table_schema + tree.NewDString(table.Name), // table_name + partitionName, // partition_name + subpartitionName, // subpartition_name + partitionOrdinal, // partition_ordinal_position + subpartitionOrdinal, // subpartition_ordinal_position + partitionMethodDatum, // partition_method + subpartitionMethod, // subpartition_method + partitionExpr, // partition_expression + subpartitionExpr, // subpartition_expression + description, // partition_description + tree.DNull, // table_rows + tree.DNull, // avg_row_length + tree.DNull, // data_length + tree.DNull, // max_data_length + tree.DNull, // index_length + tree.DNull, // data_free + tree.DNull, // create_time + tree.DNull, // update_time + tree.DNull, // check_time + tree.DNull, // checksum + tree.DNull, // partition_comment + tree.DNull, // nodegroup + tree.DNull, // tablespace_name + ) + } - // We don't need real prefixes in the DecodePartitionTuple calls because we - // only use the tree.Datums part of the output. - fakePrefixDatums := make([]tree.Datum, colOffset) - for i := range fakePrefixDatums { - fakePrefixDatums[i] = tree.DNull - } + // This produces the list_value column. + for _, l := range partitioning.List { + var buf bytes.Buffer + for j, values := range l.Values { + if j != 0 { + buf.WriteString(`, `) + } + tuple, _, err := sqlbase.DecodePartitionTuple( + &datumAlloc, table, index, partitioning, values, fakePrefixDatums, + ) + if err != nil { + return err + } + buf.WriteString(tuple.String()) + } - // This produces the list_value column. - for _, l := range partitioning.List { - var buf bytes.Buffer - for j, values := range l.Values { - if j != 0 { - buf.WriteString(`, `) - } - tuple, _, err := sqlbase.DecodePartitionTuple( - &datumAlloc, table, index, partitioning, values, fakePrefixDatums, - ) - if err != nil { - return err - } - buf.WriteString(tuple.String()) - } + partitionValue := tree.NewDString(buf.String()) + name := tree.NewDString(l.Name) - partitionValue := tree.NewDString(buf.String()) - name := tree.NewDString(l.Name) + // Figure out which zone and subzone this partition should correspond to. + zoneID, zone, subzone, err := GetZoneConfigInTxn( + ctx, p.txn, uint32(table.ID), index, l.Name, false /* getInheritedDefault */) + if err != nil { + return err + } + subzoneID := base.SubzoneID(0) + if subzone != nil { + for i, s := range zone.Subzones { + if s.IndexID == subzone.IndexID && s.PartitionName == subzone.PartitionName { + subzoneID = base.SubzoneIDFromIndex(i) + } + } + } - // Figure out which zone and subzone this partition should correspond to. - zoneID, zone, subzone, err := GetZoneConfigInTxn( - ctx, p.txn, uint32(table.ID), index, l.Name, false /* getInheritedDefault */) - if err != nil { - return err - } - subzoneID := base.SubzoneID(0) - if subzone != nil { - for i, s := range zone.Subzones { - if s.IndexID == subzone.IndexID && s.PartitionName == subzone.PartitionName { - subzoneID = base.SubzoneIDFromIndex(i) + if err := addPartitionRow( + name, + partitionValue, + tree.DNull, /* rangeValue */ + tree.DNull, /* hashValue */ + partitionValue, + zoneID, + subzoneID, + ); err != nil { + return err + } + // Seed the root context at the first level, then carry it unchanged + // through recursive visits so deeper rows still reference the top level. + rootName := tree.Datum(name) + rootOrdinal := tree.Datum(tree.NewDString(strconv.Itoa(ordinal))) + rootMethod := method + rootExpression := tree.Datum(colNames) + if parent.isSubpartition { + rootName = parent.rootName + rootOrdinal = parent.rootOrdinal + rootMethod = parent.rootMethod + rootExpression = parent.rootExpression + } + childParent := partitionParentInfo{ + isSubpartition: true, + name: name, + rootName: rootName, + rootOrdinal: rootOrdinal, + rootMethod: rootMethod, + rootExpression: rootExpression, + } + err = addPartitioningRowsWithContext(ctx, p, database, schema, table, index, &l.Subpartitioning, + childParent, colOffset+int(partitioning.NumColumns), addRow) + if err != nil { + return err } } - } - if err := addRow( - tableID, - indexID, - parentName, - name, - numColumns, - colNames, - partitionValue, - tree.DNull, /* null value for partition range */ - tree.DNull, /* null value for partition hash */ - tree.NewDInt(tree.DInt(zoneID)), - tree.NewDInt(tree.DInt(subzoneID)), - ); err != nil { - return err - } - err = addPartitioningRows(ctx, p, database, table, index, &l.Subpartitioning, name, - colOffset+int(partitioning.NumColumns), addRow) - if err != nil { - return err - } - } + // This produces the range_value column. + for _, r := range partitioning.Range { + var buf bytes.Buffer + fromTuple, _, err := sqlbase.DecodePartitionTuple( + &datumAlloc, table, index, partitioning, r.FromInclusive, fakePrefixDatums, + ) + if err != nil { + return err + } + buf.WriteString(fromTuple.String()) + buf.WriteString(" TO ") + toTuple, _, err := sqlbase.DecodePartitionTuple( + &datumAlloc, table, index, partitioning, r.ToExclusive, fakePrefixDatums, + ) + if err != nil { + return err + } + buf.WriteString(toTuple.String()) + partitionRange := tree.NewDString(buf.String()) - // This produces the range_value column. - for _, r := range partitioning.Range { - var buf bytes.Buffer - fromTuple, _, err := sqlbase.DecodePartitionTuple( - &datumAlloc, table, index, partitioning, r.FromInclusive, fakePrefixDatums, - ) - if err != nil { - return err - } - buf.WriteString(fromTuple.String()) - buf.WriteString(" TO ") - toTuple, _, err := sqlbase.DecodePartitionTuple( - &datumAlloc, table, index, partitioning, r.ToExclusive, fakePrefixDatums, - ) - if err != nil { - return err - } - buf.WriteString(toTuple.String()) - partitionRange := tree.NewDString(buf.String()) + // Figure out which zone and subzone this partition should correspond to. + zoneID, zone, subzone, err := GetZoneConfigInTxn( + ctx, p.txn, uint32(table.ID), index, r.Name, false /* getInheritedDefault */) + if err != nil { + return err + } + subzoneID := base.SubzoneID(0) + if subzone != nil { + for i, s := range zone.Subzones { + if s.IndexID == subzone.IndexID && s.PartitionName == subzone.PartitionName { + subzoneID = base.SubzoneIDFromIndex(i) + } + } + } - // Figure out which zone and subzone this partition should correspond to. - zoneID, zone, subzone, err := GetZoneConfigInTxn( - ctx, p.txn, uint32(table.ID), index, r.Name, false /* getInheritedDefault */) - if err != nil { - return err - } - subzoneID := base.SubzoneID(0) - if subzone != nil { - for i, s := range zone.Subzones { - if s.IndexID == subzone.IndexID && s.PartitionName == subzone.PartitionName { - subzoneID = base.SubzoneIDFromIndex(i) + if err := addPartitionRow( + tree.NewDString(r.Name), + tree.DNull, /* listValue */ + partitionRange, + tree.DNull, /* hashValue */ + partitionRange, + zoneID, + subzoneID, + ); err != nil { + return err } } - } - if err := addRow( - tableID, - indexID, - parentName, - tree.NewDString(r.Name), - numColumns, - colNames, - tree.DNull, /* null value for partition list */ - partitionRange, - tree.DNull, /* null value for partition hash */ - tree.NewDInt(tree.DInt(zoneID)), - tree.NewDInt(tree.DInt(subzoneID)), - ); err != nil { - return err - } - } + // This produces the hash_value column. + for _, r := range partitioning.HashPoint { + var buff bytes.Buffer + if r.HashPoints != nil { + for j, point := range r.HashPoints { + if j != 0 { + buff.WriteString(`, `) + } + buff.WriteString("(" + strconv.Itoa(int(point)) + ")") + } + } else if r.ToPoint-r.FromPoint > 0 { + from := strconv.Itoa(int(r.FromPoint)) + buff.WriteString("(" + from + ")") + buff.WriteString(" TO ") + to := strconv.Itoa(int(r.ToPoint)) + buff.WriteString("(" + to + ")") + } + partitionHash := tree.NewDString(buff.String()) - // This produces the hash_value column. - for _, r := range partitioning.HashPoint { - var buff bytes.Buffer - if r.HashPoints != nil { - for j, point := range r.HashPoints { - if j != 0 { - buff.WriteString(`, `) + // Figure out which zone and subzone this partition should correspond to. + zoneID, zone, subzone, err := GetZoneConfigInTxn( + ctx, p.txn, uint32(table.ID), index, r.Name, false /* getInheritedDefault */) + if err != nil { + return err + } + subzoneID := base.SubzoneID(0) + if subzone != nil { + for i, s := range zone.Subzones { + if s.IndexID == subzone.IndexID && s.PartitionName == subzone.PartitionName { + subzoneID = base.SubzoneIDFromIndex(i) + } + } } - buff.WriteString("(" + strconv.Itoa(int(point)) + ")") - } - } else if r.ToPoint-r.FromPoint > 0 { - from := strconv.Itoa(int(r.FromPoint)) - buff.WriteString("(" + from + ")") - buff.WriteString(" TO ") - to := strconv.Itoa(int(r.ToPoint)) - buff.WriteString("(" + to + ")") - } - partitionHash := tree.NewDString(buff.String()) - // Figure out which zone and subzone this partition should correspond to. - zoneID, zone, subzone, err := GetZoneConfigInTxn( - ctx, p.txn, uint32(table.ID), index, r.Name, false /* getInheritedDefault */) - if err != nil { - return err - } - subzoneID := base.SubzoneID(0) - if subzone != nil { - for i, s := range zone.Subzones { - if s.IndexID == subzone.IndexID && s.PartitionName == subzone.PartitionName { - subzoneID = base.SubzoneIDFromIndex(i) + if err := addPartitionRow( + tree.NewDString(r.Name), + tree.DNull, /* listValue */ + tree.DNull, /* rangeValue */ + partitionHash, + partitionHash, + zoneID, + subzoneID, + ); err != nil { + return err } } - } - - if err := addRow( - tableID, - indexID, - parentName, - tree.NewDString(r.Name), - numColumns, - colNames, - tree.DNull, /* null value for partition list */ - tree.DNull, /* null value for partition list */ - partitionHash, - tree.NewDInt(tree.DInt(zoneID)), - tree.NewDInt(tree.DInt(subzoneID)), - ); err != nil { - return err - } - } - - return nil -} -// kwdbInternalPartitionsTable decodes and exposes the partitions of each -// table. -// -// TODO(tbg): prefix with cluster_. -var kwdbInternalPartitionsTable = virtualSchemaTable{ - comment: "defined partitions for all tables/indexes accessible by the current user in the current database (KV scan)", - schema: ` -CREATE TABLE kwdb_internal.partitions ( - table_id INT8 NOT NULL, - index_id INT8 NOT NULL, - parent_name STRING, - name STRING NOT NULL, - columns INT8 NOT NULL, - column_names STRING, - list_value STRING, - range_value STRING, - hash_value STRING, - zone_id INT8, -- references a zone id in the kwdb_internal.zones table - subzone_id INT8 -- references a subzone id in the kwdb_internal.zones table -) - `, - populate: func(ctx context.Context, p *planner, dbContext *DatabaseDescriptor, addRow func(...tree.Datum) error) error { - dbName := "" - if dbContext != nil { - dbName = dbContext.Name + return nil } return forEachTableDescAll(ctx, p, dbContext, hideVirtual, /* virtual tables have no partitions*/ - func(db *DatabaseDescriptor, _ string, table *TableDescriptor) error { + func(db *DatabaseDescriptor, scName string, table *TableDescriptor) error { return table.ForeachNonDropIndex(func(index *sqlbase.IndexDescriptor) error { - return addPartitioningRows(ctx, p, dbName, table, index, &index.Partitioning, - tree.DNull /* parentName */, 0 /* colOffset */, addRow) + return addPartitioningRowsWithContext(ctx, p, db.Name, scName, table, index, &index.Partitioning, + partitionParentInfo{name: tree.DNull}, 0 /* colOffset */, addRow) }) }) }, diff --git a/kwbase/pkg/sql/kwdb_internal_test.go b/kwbase/pkg/sql/kwdb_internal_test.go index ec2e496c84..d4ced518a0 100644 --- a/kwbase/pkg/sql/kwdb_internal_test.go +++ b/kwbase/pkg/sql/kwdb_internal_test.go @@ -593,74 +593,3 @@ func TestKWDBInternalTablesPopulate(t *testing.T) { }) } } - -// TestAddPartitioningRows tests the addPartitioningRows function -func TestAddPartitioningRows(t *testing.T) { - defer leaktest.AfterTest(t)() - - // Start a test server - ctx := context.Background() - s, _, db := serverutils.StartServer(t, base.TestServerArgs{}) - defer s.Stopper().Stop(ctx) - - // Get the executor config - execCfg := s.ExecutorConfig().(ExecutorConfig) - - // Create a planner with admin privileges - localPlanner, cleanup := NewInternalPlanner( - "test", - kv.NewTxn(ctx, db, s.NodeID()), - security.RootUser, // Root user has admin privileges - &MemoryMetrics{}, - &execCfg, - ) - defer cleanup() - p := localPlanner.(*planner) - - // Create a mock table descriptor with partitioning - table := &TableDescriptor{ - ID: 1, - Name: "test_table", - Indexes: []sqlbase.IndexDescriptor{ - { - ID: 1, - Name: "primary", - Partitioning: sqlbase.PartitioningDescriptor{ - NumColumns: 1, - List: []sqlbase.PartitioningDescriptor_List{ - { - Name: "partition1", - Values: [][]byte{[]byte("value1")}, - }, - { - Name: "partition2", - Values: [][]byte{[]byte("value2")}, - }, - }, - }, - ColumnNames: []string{"col1"}, - }, - }, - } - - // Create a slice to capture the rows added by addPartitioningRows - var rows [][]tree.Datum - addRow := func(d ...tree.Datum) error { - rows = append(rows, d) - return nil - } - - // Call addPartitioningRows - err := addPartitioningRows(ctx, p, "test_db", table, &table.Indexes[0], &table.Indexes[0].Partitioning, tree.DNull, 0, addRow) - // We expect this to fail in test environment due to node availability issues - // but we just want to make sure it doesn't panic - if err != nil { - t.Logf("addPartitioningRows returned error (expected in test environment): %v", err) - } - - // Even if there's an error, we still check if any rows were added - // This helps us verify that the function is working partially at least - if len(rows) == 0 { - t.Logf("no rows were added, but this might be expected in test environment") - } -} diff --git a/kwbase/pkg/sql/logictest/testdata/logic_test/kwdb_internal b/kwbase/pkg/sql/logictest/testdata/logic_test/kwdb_internal index a60fc4c31c..0d1259cd0b 100644 --- a/kwbase/pkg/sql/logictest/testdata/logic_test/kwdb_internal +++ b/kwbase/pkg/sql/logictest/testdata/logic_test/kwdb_internal @@ -628,3 +628,95 @@ query B SELECT kwdb_internal.is_admin() ---- false + +subtest partitions_mysql_compatibility + +user root + +statement ok +CREATE DATABASE partition_view_db + +statement ok +CREATE TABLE partition_view_db.partitioned_table ( + region INT, + id INT, + PRIMARY KEY (region, id) +) PARTITION BY LIST (region) ( + PARTITION p_east VALUES IN (1), + PARTITION p_west VALUES IN (2) +) + +statement ok +CREATE TABLE partition_view_db.range_partitioned_table ( + id INT PRIMARY KEY +) PARTITION BY RANGE (id) ( + PARTITION p_low VALUES FROM (MINVALUE) TO (10), + PARTITION p_high VALUES FROM (10) TO (MAXVALUE) +) + +statement ok +CREATE TABLE partition_view_db.hash_partitioned_table ( + bucket INT PRIMARY KEY +) PARTITION BY HASH (bucket) ( + PARTITION p_hash_0 VALUES IN (0), + PARTITION p_hash_1 VALUES IN (1) +) + +# Verify that the legacy columns remain populated while the appended columns +# expose the same partition nodes using the MySQL-compatible representation. +query BBTITBBTIB rowsort +SELECT table_id > 0, + index_id > 0, + name, + columns, + column_names, + parent_name IS NULL, + list_value IS NOT NULL, + partition_name, + partition_ordinal_position::INT, + partition_description = list_value + FROM partition_view_db.kwdb_internal.partitions + WHERE table_name = 'partitioned_table' +---- +true true p_east 1 region true true p_east 1 true +true true p_west 1 region true true p_west 2 true + +query TTTTTTTTTTTBB rowsort +SELECT table_catalog, + table_schema, + table_name, + partition_name, + subpartition_name, + partition_ordinal_position, + subpartition_ordinal_position, + partition_method, + subpartition_method, + partition_expression, + subpartition_expression, + table_rows IS NULL AND avg_row_length IS NULL AND data_length IS NULL AND + max_data_length IS NULL AND index_length IS NULL AND data_free IS NULL, + create_time IS NULL AND update_time IS NULL AND check_time IS NULL AND + checksum IS NULL AND partition_comment IS NULL AND nodegroup IS NULL AND + tablespace_name IS NULL + FROM partition_view_db.kwdb_internal.partitions + WHERE table_name = 'partitioned_table' +---- +partition_view_db public partitioned_table p_east NULL 1 NULL LIST NULL region NULL true true +partition_view_db public partitioned_table p_west NULL 2 NULL LIST NULL region NULL true true + +# Each supported partition method reuses its decoded legacy value as the +# compatibility description instead of decoding the descriptor a second time. +query TTTB rowsort +SELECT table_name, + partition_name, + partition_method, + partition_description = coalesce(list_value, range_value, hash_value) + FROM partition_view_db.kwdb_internal.partitions + WHERE table_name IN ('partitioned_table', 'range_partitioned_table', 'hash_partitioned_table') +---- +hash_partitioned_table p_hash_0 HASH true +hash_partitioned_table p_hash_1 HASH true +partitioned_table p_east LIST true +partitioned_table p_west LIST true +range_partitioned_table p_high RANGE true +range_partitioned_table p_low RANGE true -- Gitee From c440bc22c27f2acf0127451336550447a4131d76 Mon Sep 17 00:00:00 2001 From: Yilin Date: Wed, 30 Sep 2026 16:25:05 +0800 Subject: [PATCH 03/18] test: cover column statistics view --- .../testdata/logic_test/column_statistics | 140 ++++++++++++++++++ .../logictest/testdata/logic_test/grant_table | 1 + .../testdata/logic_test/information_schema | 6 + .../logictest/testdata/logic_test/pg_catalog | 1 + 4 files changed, 148 insertions(+) create mode 100644 kwbase/pkg/sql/logictest/testdata/logic_test/column_statistics diff --git a/kwbase/pkg/sql/logictest/testdata/logic_test/column_statistics b/kwbase/pkg/sql/logictest/testdata/logic_test/column_statistics new file mode 100644 index 0000000000..c9ce3a131c --- /dev/null +++ b/kwbase/pkg/sql/logictest/testdata/logic_test/column_statistics @@ -0,0 +1,140 @@ +# LogicTest: local + +statement ok +CREATE DATABASE column_statistics_db + +statement ok +SET DATABASE = column_statistics_db + +statement ok +CREATE TABLE visible_stats (id INT PRIMARY KEY, value INT, category STRING) + +statement ok +CREATE TABLE hidden_stats (id INT PRIMARY KEY, value INT) + +statement ok +CREATE TABLE multi_only_stats (a INT, b INT) + +statement ok +CREATE TABLE equi_height_stats (id INT PRIMARY KEY, value INT) + +statement count 5 +INSERT INTO visible_stats VALUES (1, 10, 'a'), (2, 10, 'a'), (3, 20, 'b'), (4, 30, 'c'), (5, NULL, 'c') + +statement count 2 +INSERT INTO hidden_stats VALUES (1, 1), (2, 2) + +statement count 2 +INSERT INTO multi_only_stats VALUES (1, 10), (2, 20) + +statement count 400 +INSERT INTO equi_height_stats SELECT i, i FROM generate_series(1, 400) AS g(i) + +statement ok +CREATE STATISTICS visible_value ON value FROM visible_stats + +statement ok +CREATE STATISTICS visible_multi ON value, category FROM visible_stats + +statement ok +CREATE STATISTICS hidden_value ON value FROM hidden_stats + +statement ok +CREATE STATISTICS multi_only_ab ON a, b FROM multi_only_stats + +statement ok +CREATE STATISTICS equi_height_value ON value FROM equi_height_stats + +query TTBTTTBB colnames +SHOW COLUMNS FROM information_schema.column_statistics +---- +column_name data_type is_nullable column_default generation_expression indices is_hidden is_tag +schema_name STRING false NULL · {} false false +table_name STRING false NULL · {} false false +column_name STRING false NULL · {} false false +histogram JSONB false NULL · {} false false + +# Multi-column statistics do not create a row. The generated single-column +# histogram uses the supported MySQL JSON keys and omits unavailable metadata. +query TTTTIBB +SELECT schema_name, + table_name, + column_name, + histogram->>'histogram-type', + (histogram->>'number-of-buckets-specified')::INT, + histogram ? 'sampling-rate', + histogram ? 'collation-id' + FROM information_schema.column_statistics + WHERE table_name = 'visible_stats' + ORDER BY column_name +---- +public visible_stats value singleton 3 false false + +query BBT +SELECT (histogram->>'null-values')::DECIMAL = 0.2, + jsonb_array_length(histogram->'buckets') > 0, + histogram->>'data-type' + FROM information_schema.column_statistics + WHERE table_name = 'visible_stats' AND column_name = 'value' +---- +true true int + +# A table with only multi-column statistics produces no rows because this view +# identifies each histogram with exactly one COLUMN_NAME. +query I +SELECT count(*) + FROM information_schema.column_statistics + WHERE table_name = 'multi_only_stats' +---- +0 + +# More distinct values than the histogram bucket limit produce an equi-height +# histogram. The first bucket has equal display bounds, later buckets reuse the +# preceding upper bound, cumulative frequencies increase, and the final one is 1. +query TIBBBB +SELECT histogram->>'histogram-type', + (histogram->>'number-of-buckets-specified')::INT, + histogram->'buckets'->0->>0 = histogram->'buckets'->0->>1, + histogram->'buckets'->0->>1 = histogram->'buckets'->1->>0, + (histogram->'buckets'->1->>2)::DECIMAL > (histogram->'buckets'->0->>2)::DECIMAL, + (histogram->'buckets'->199->>2)::DECIMAL = 1 + FROM information_schema.column_statistics + WHERE table_name = 'equi_height_stats' AND column_name = 'value' +---- +equi-height 200 true true true true + +# A newer statistic for the same column replaces the older display row. +statement count 1 +INSERT INTO visible_stats VALUES (6, 40, 'd') + +statement ok +CREATE STATISTICS visible_value_new ON value FROM visible_stats + +query IB +SELECT count(*), + (min((histogram->>'null-values')::DECIMAL) < 0.17 AND + min((histogram->>'null-values')::DECIMAL) > 0.16) + FROM information_schema.column_statistics + WHERE table_name = 'visible_stats' AND column_name = 'value' +---- +1 true + +statement ok +GRANT SELECT ON visible_stats TO testuser + +user testuser + +# Table visibility applies before statistics are read, so a non-admin user +# cannot discover histograms belonging to an inaccessible table. +query TT +SELECT table_name, column_name + FROM information_schema.column_statistics + WHERE table_name IN ('visible_stats', 'hidden_stats') + ORDER BY table_name, column_name +---- +visible_stats value + +user root + +statement ok +DROP DATABASE column_statistics_db CASCADE diff --git a/kwbase/pkg/sql/logictest/testdata/logic_test/grant_table b/kwbase/pkg/sql/logictest/testdata/logic_test/grant_table index 1fbdc67f22..90bb384390 100644 --- a/kwbase/pkg/sql/logictest/testdata/logic_test/grant_table +++ b/kwbase/pkg/sql/logictest/testdata/logic_test/grant_table @@ -37,6 +37,7 @@ test information_schema administrable_role_authorizations NULL test information_schema applicable_roles NULL public SELECT test information_schema check_constraints NULL public SELECT test information_schema column_privileges NULL public SELECT +test information_schema column_statistics NULL public SELECT test information_schema columns NULL public SELECT test information_schema constraint_column_usage NULL public SELECT test information_schema enabled_roles NULL public SELECT diff --git a/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema b/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema index 544d09d5c3..90f947e406 100644 --- a/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema +++ b/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema @@ -19,6 +19,7 @@ administrable_role_authorizations SYSTEM VIEW applicable_roles SYSTEM VIEW check_constraints SYSTEM VIEW column_privileges SYSTEM VIEW +column_statistics SYSTEM VIEW columns SYSTEM VIEW constraint_column_usage SYSTEM VIEW enabled_roles SYSTEM VIEW @@ -130,6 +131,7 @@ administrable_role_authorizations SYSTEM VIEW applicable_roles SYSTEM VIEW check_constraints SYSTEM VIEW column_privileges SYSTEM VIEW +column_statistics SYSTEM VIEW columns SYSTEM VIEW constraint_column_usage SYSTEM VIEW enabled_roles SYSTEM VIEW @@ -255,6 +257,7 @@ information_schema administrable_role_authorizations information_schema applicable_roles information_schema check_constraints information_schema column_privileges +information_schema column_statistics information_schema columns information_schema constraint_column_usage information_schema enabled_roles @@ -438,6 +441,7 @@ administrable_role_authorizations applicable_roles check_constraints column_privileges +column_statistics columns constraint_column_usage enabled_roles @@ -604,6 +608,7 @@ system information_schema administrable_role_authorizations SYSTEM VIE system information_schema applicable_roles SYSTEM VIEW NO 1 3819883323 NULL NULL NULL NULL NULL NO NULL NULL NULL NULL NULL system information_schema check_constraints SYSTEM VIEW NO 1 3819883323 NULL NULL NULL NULL NULL NO NULL NULL NULL NULL NULL system information_schema column_privileges SYSTEM VIEW NO 1 3819883323 NULL NULL NULL NULL NULL NO NULL NULL NULL NULL NULL +system information_schema column_statistics SYSTEM VIEW NO 1 3819883323 NULL NULL NULL NULL NULL NO NULL NULL NULL NULL NULL system information_schema columns SYSTEM VIEW NO 1 3819883323 NULL NULL NULL NULL NULL NO NULL NULL NULL NULL NULL system information_schema constraint_column_usage SYSTEM VIEW NO 1 3819883323 NULL NULL NULL NULL NULL NO NULL NULL NULL NULL NULL system information_schema enabled_roles SYSTEM VIEW NO 1 3819883323 NULL NULL NULL NULL NULL NO NULL NULL NULL NULL NULL @@ -1957,6 +1962,7 @@ NULL public system information_schema administrable_role_authoriz NULL public system information_schema applicable_roles SELECT NULL YES NULL public system information_schema check_constraints SELECT NULL YES NULL public system information_schema column_privileges SELECT NULL YES +NULL public system information_schema column_statistics SELECT NULL YES NULL public system information_schema columns SELECT NULL YES NULL public system information_schema constraint_column_usage SELECT NULL YES NULL public system information_schema enabled_roles SELECT NULL YES diff --git a/kwbase/pkg/sql/logictest/testdata/logic_test/pg_catalog b/kwbase/pkg/sql/logictest/testdata/logic_test/pg_catalog index 68d2e56082..e57da908d7 100644 --- a/kwbase/pkg/sql/logictest/testdata/logic_test/pg_catalog +++ b/kwbase/pkg/sql/logictest/testdata/logic_test/pg_catalog @@ -1555,6 +1555,7 @@ objoid classoid objsubid description 4294967248 4294967219 0 roles available to the current user 4294967247 4294967219 0 check constraints 4294967246 4294967219 0 column privilege grants (incomplete) +4294967167 4294967219 0 histogram statistics for visible table columns 4294967245 4294967219 0 table and view columns (incomplete) 4294967244 4294967219 0 columns usage by constraints 4294967243 4294967219 0 roles for the current user -- Gitee From 06a93ee222a13e9d458eeaa1682344888d7dae1c Mon Sep 17 00:00:00 2001 From: Yilin Date: Wed, 30 Sep 2026 19:36:24 +0800 Subject: [PATCH 04/18] sql: add information schema processlist view --- kwbase/pkg/sql/information_schema.go | 81 +++++++++++++++++++ .../logictest/testdata/logic_test/grant_table | 1 + .../testdata/logic_test/information_schema | 72 +++++++++++++++++ .../logictest/testdata/logic_test/pg_catalog | 1 + kwbase/pkg/sql/sqlbase/constants.go | 4 +- 5 files changed, 158 insertions(+), 1 deletion(-) diff --git a/kwbase/pkg/sql/information_schema.go b/kwbase/pkg/sql/information_schema.go index ede3a06a0d..8232a5e4c0 100644 --- a/kwbase/pkg/sql/information_schema.go +++ b/kwbase/pkg/sql/information_schema.go @@ -30,6 +30,7 @@ import ( "sort" "strconv" "strings" + "time" "unicode/utf8" "gitee.com/kwbasedb/kwbase/pkg/base" @@ -46,6 +47,7 @@ import ( "gitee.com/kwbasedb/kwbase/pkg/sql/vtable" "gitee.com/kwbasedb/kwbase/pkg/util/log" "gitee.com/kwbasedb/kwbase/pkg/util/protoutil" + "gitee.com/kwbasedb/kwbase/pkg/util/timeutil" "github.com/cockroachdb/errors" ) @@ -102,6 +104,7 @@ var informationSchema = virtualSchema{ "key_column_usage", "keywords", "parameters", + "processlist", "referential_constraints", "role_column_grants", "role_routine_grants", @@ -147,6 +150,7 @@ var informationSchema = virtualSchema{ sqlbase.InformationSchemaKeyColumnUsageTableID: informationSchemaKeyColumnUsageTable, sqlbase.InformationSchemaKeywordsTableID: informationSchemaKeywordsTable, sqlbase.InformationSchemaParametersTableID: informationSchemaParametersTable, + sqlbase.InformationSchemaProcesslistTableID: informationSchemaProcesslistTable, sqlbase.InformationSchemaReferentialConstraintsTableID: informationSchemaReferentialConstraintsTable, sqlbase.InformationSchemaRoleTableGrantsID: informationSchemaRoleTableGrants, sqlbase.InformationSchemaRoutineTableID: informationSchemaRoutineTable, @@ -806,6 +810,83 @@ CREATE TABLE information_schema.parameters ( }, } +// MySQL: https://dev.mysql.com/doc/refman/8.0/en/information-schema-processlist-table.html +var informationSchemaProcesslistTable = virtualSchemaTable{ + comment: `current SQL sessions visible to the current user (cluster RPC; partial MySQL compatibility)`, + schema: ` +CREATE TABLE information_schema.processlist ( + ID STRING, + USER STRING, + HOST STRING, + DB STRING, + COMMAND STRING, + TIME INT8, + STATE STRING, + INFO STRING +)`, + populate: func(ctx context.Context, p *planner, _ *DatabaseDescriptor, addRow func(...tree.Datum) error) error { + req := p.makeSessionsRequest(ctx) + response, err := p.extendedEvalCtx.StatusServer.ListSessions(ctx, &req) + if err != nil { + return err + } + + // Read the session list once so every row in this statement is derived + // from the same response. This is not a cluster-wide atomic snapshot. + snapshotTime := timeutil.Now() + for _, session := range response.Sessions { + command := tree.NewDString("Sleep") + var elapsedTime, state, info tree.Datum = tree.DNull, tree.DNull, tree.DNull + + // MySQL processlist exposes one row per connection. A KWDB session can + // report multiple active queries, so use the oldest one as its representative. + // The slice has no ordering guarantee; -1 means no active query has been seen. + oldestQueryIdx := -1 + for i := range session.ActiveQueries { + // Keep the first query initially, then replace it whenever an earlier + // start time is found later in the slice. + if oldestQueryIdx == -1 || + session.ActiveQueries[i].Start.Before(session.ActiveQueries[oldestQueryIdx].Start) { + oldestQueryIdx = i + } + } + if oldestQueryIdx >= 0 { + query := session.ActiveQueries[oldestQueryIdx] + elapsed := snapshotTime.Sub(query.Start) / time.Second + // Remote node clock skew can make a newly started query appear to begin + // after the local snapshot time; process durations must not be negative. + if elapsed < 0 { + elapsed = 0 + } + command = tree.NewDString("Query") + elapsedTime = tree.NewDInt(tree.DInt(elapsed)) + state = tree.NewDString(query.Phase.String()) + info = tree.NewDString(query.Sql) + } + + if err := addRow( + getSessionID(session), // id + tree.NewDString(session.Username), // user + tree.NewDString(session.ClientAddress), // host + tree.DNull, // db: current database is not recorded + command, // command + elapsedTime, // time + state, // state + info, // info + ); err != nil { + return err + } + } + + // Node errors are not sessions. Preserve rows collected from healthy nodes + // and report failures without synthesizing processlist entries for them. + for _, rpcErr := range response.Errors { + log.Warning(ctx, rpcErr.Message) + } + return nil + }, +} + var ( matchOptionFull = tree.NewDString("FULL") matchOptionPartial = tree.NewDString("PARTIAL") diff --git a/kwbase/pkg/sql/logictest/testdata/logic_test/grant_table b/kwbase/pkg/sql/logictest/testdata/logic_test/grant_table index 90bb384390..e2b294ba6e 100644 --- a/kwbase/pkg/sql/logictest/testdata/logic_test/grant_table +++ b/kwbase/pkg/sql/logictest/testdata/logic_test/grant_table @@ -44,6 +44,7 @@ test information_schema enabled_roles NULL test information_schema key_column_usage NULL public SELECT test information_schema keywords NULL public SELECT test information_schema parameters NULL public SELECT +test information_schema processlist NULL public SELECT test information_schema procedure_privileges NULL public SELECT test information_schema referential_constraints NULL public SELECT test information_schema role_table_grants NULL public SELECT diff --git a/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema b/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema index 90f947e406..e3594b421f 100644 --- a/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema +++ b/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema @@ -26,6 +26,7 @@ enabled_roles SYSTEM VIEW key_column_usage SYSTEM VIEW keywords SYSTEM VIEW parameters SYSTEM VIEW +processlist SYSTEM VIEW procedure_privileges SYSTEM VIEW referential_constraints SYSTEM VIEW role_table_grants SYSTEM VIEW @@ -138,6 +139,7 @@ enabled_roles SYSTEM VIEW key_column_usage SYSTEM VIEW keywords SYSTEM VIEW parameters SYSTEM VIEW +processlist SYSTEM VIEW procedure_privileges SYSTEM VIEW referential_constraints SYSTEM VIEW role_table_grants SYSTEM VIEW @@ -264,6 +266,7 @@ information_schema enabled_roles information_schema key_column_usage information_schema keywords information_schema parameters +information_schema processlist information_schema procedure_privileges information_schema referential_constraints information_schema role_table_grants @@ -448,6 +451,7 @@ enabled_roles key_column_usage keywords parameters +processlist procedure_privileges referential_constraints role_table_grants @@ -615,6 +619,7 @@ system information_schema enabled_roles SYSTEM VIE system information_schema key_column_usage SYSTEM VIEW NO 1 3819883323 NULL NULL NULL NULL NULL NO NULL NULL NULL NULL NULL system information_schema keywords SYSTEM VIEW NO 1 3819883323 NULL NULL NULL NULL NULL NO NULL NULL NULL NULL NULL system information_schema parameters SYSTEM VIEW NO 1 3819883323 NULL NULL NULL NULL NULL NO NULL NULL NULL NULL NULL +system information_schema processlist SYSTEM VIEW NO 1 3819883323 NULL NULL NULL NULL NULL NO NULL NULL NULL NULL NULL system information_schema procedure_privileges SYSTEM VIEW NO 1 3819883323 NULL NULL NULL NULL NULL NO NULL NULL NULL NULL NULL system information_schema referential_constraints SYSTEM VIEW NO 1 3819883323 NULL NULL NULL NULL NULL NO NULL NULL NULL NULL NULL system information_schema role_table_grants SYSTEM VIEW NO 1 3819883323 NULL NULL NULL NULL NULL NO NULL NULL NULL NULL NULL @@ -1969,6 +1974,7 @@ NULL public system information_schema enabled_roles NULL public system information_schema key_column_usage SELECT NULL YES NULL public system information_schema keywords SELECT NULL YES NULL public system information_schema parameters SELECT NULL YES +NULL public system information_schema processlist SELECT NULL YES NULL public system information_schema procedure_privileges SELECT NULL YES NULL public system information_schema referential_constraints SELECT NULL YES NULL public system information_schema role_table_grants SELECT NULL YES @@ -2774,6 +2780,72 @@ SELECT * FROM information_schema.parameters ---- specific_catalog specific_schema specific_name ordinal_position parameter_mode is_result as_locator parameter_name data_type character_maximum_length character_octet_length character_set_catalog character_set_schema character_set_name collation_catalog collation_schema collation_name numeric_precision numeric_precision_radix numeric_scale datetime_precision interval_type interval_precision udt_catalog udt_schema udt_name scope_catalog scope_schema scope_name maximum_cardinality dtd_identifier parameter_default +# test information_schema.processlist + +query TTBTTTBB colnames +SHOW COLUMNS FROM information_schema.processlist +---- +column_name data_type is_nullable column_default generation_expression indices is_hidden is_tag +id STRING true NULL · {} false false +user STRING true NULL · {} false false +host STRING true NULL · {} false false +db STRING true NULL · {} false false +command STRING true NULL · {} false false +time INT8 true NULL · {} false false +state STRING true NULL · {} false false +info STRING true NULL · {} false false + +# The query itself is active while processlist takes its single session snapshot. +query B +SELECT count(*) > 0 + FROM information_schema.processlist AS p + WHERE p."user" = 'root' + AND length(p.id) = 32 + AND p.db IS NULL + AND p.command = 'Query' + AND p."time" >= 0 + AND p.state IN ('PREPARING', 'EXECUTING') + AND p.info LIKE '%information_schema.processlist%' +---- +true + +user testuser + +# A non-admin user can see only sessions belonging to that same user. +query I +SELECT count(*) + FROM information_schema.processlist AS p + WHERE p."user" <> 'testuser' +---- +0 + +query B +SELECT count(*) > 0 + FROM information_schema.processlist AS p + WHERE p."user" = 'testuser' + AND length(p.id) = 32 + AND p.command = 'Query' + AND p."time" >= 0 + AND p.state IN ('PREPARING', 'EXECUTING') + AND p.info LIKE '%information_schema.processlist%' +---- +true + +user root + +# The retained testuser connection is idle after switching back to root. +query B +SELECT count(*) > 0 + FROM information_schema.processlist AS p + WHERE p."user" = 'testuser' + AND p.command = 'Sleep' + AND p.db IS NULL + AND p."time" IS NULL + AND p.state IS NULL + AND p.info IS NULL +---- +true + # information_schema.administrable_role_authorizations query TTT colnames,rowsort diff --git a/kwbase/pkg/sql/logictest/testdata/logic_test/pg_catalog b/kwbase/pkg/sql/logictest/testdata/logic_test/pg_catalog index e57da908d7..b69f2b24be 100644 --- a/kwbase/pkg/sql/logictest/testdata/logic_test/pg_catalog +++ b/kwbase/pkg/sql/logictest/testdata/logic_test/pg_catalog @@ -1562,6 +1562,7 @@ objoid classoid objsubid description 4294967242 4294967219 0 column usage by indexes and key constraints 4294967169 4294967219 0 SQL keywords 4294967241 4294967219 0 built-in function parameters (empty - introspection not yet supported) +4294967166 4294967219 0 current SQL sessions visible to the current user (cluster RPC; partial MySQL compatibility) 4294967228 4294967219 0 privileges granted on procedure (incomplete; may contain excess users or roles) 4294967240 4294967219 0 foreign key constraints 4294967239 4294967219 0 privileges granted on table or views (incomplete; see also information_schema.table_privileges; may contain excess users or roles) diff --git a/kwbase/pkg/sql/sqlbase/constants.go b/kwbase/pkg/sql/sqlbase/constants.go index dd42dba7bc..4af28a0e43 100644 --- a/kwbase/pkg/sql/sqlbase/constants.go +++ b/kwbase/pkg/sql/sqlbase/constants.go @@ -249,7 +249,9 @@ const ( CrdbInternalTSTransactionRecordID InformationSchemaKeywordsTableID InformationSchemaUserAttributesTableID - MinVirtualID = InformationSchemaUserAttributesTableID + InformationSchemaColumnStatisticsTableID + InformationSchemaProcesslistTableID + MinVirtualID = InformationSchemaProcesslistTableID ) const ( -- Gitee From 26fddb768682cf5400759d98dd1952b41a47bb85 Mon Sep 17 00:00:00 2001 From: Yilin Date: Wed, 30 Sep 2026 22:15:02 +0800 Subject: [PATCH 05/18] sql: restore column statistics system view --- kwbase/pkg/sql/information_schema.go | 179 ++++++++++++++++++ .../basic_v3/information_schema_views_04.sql | 128 +++++++++++++ .../master/ts_create_bug.sql_1n.master | 4 +- .../master/ts_create_bug.sql_5c.master | 4 +- .../master/ts_create_bug.sql_5cr.master | 4 +- 5 files changed, 313 insertions(+), 6 deletions(-) create mode 100644 qa/Integration/basic_v3/information_schema_views_04.sql diff --git a/kwbase/pkg/sql/information_schema.go b/kwbase/pkg/sql/information_schema.go index a70175bbe8..23ac0e4d89 100644 --- a/kwbase/pkg/sql/information_schema.go +++ b/kwbase/pkg/sql/information_schema.go @@ -43,8 +43,10 @@ import ( "gitee.com/kwbasedb/kwbase/pkg/sql/sem/tree" "gitee.com/kwbasedb/kwbase/pkg/sql/sessiondata" "gitee.com/kwbasedb/kwbase/pkg/sql/sqlbase" + "gitee.com/kwbasedb/kwbase/pkg/sql/stats" "gitee.com/kwbasedb/kwbase/pkg/sql/types" "gitee.com/kwbasedb/kwbase/pkg/sql/vtable" + "gitee.com/kwbasedb/kwbase/pkg/util/json" "gitee.com/kwbasedb/kwbase/pkg/util/log" "gitee.com/kwbasedb/kwbase/pkg/util/protoutil" "gitee.com/kwbasedb/kwbase/pkg/util/timeutil" @@ -84,6 +86,7 @@ var informationSchema = virtualSchema{ "column_domain_usage", "column_options", "column_privileges", + "column_statistics", "column_udt_usage", "columns", "constraint_column_usage", @@ -144,6 +147,7 @@ var informationSchema = virtualSchema{ sqlbase.InformationSchemaApplicableRolesID: informationSchemaApplicableRoles, sqlbase.InformationSchemaCheckConstraints: informationSchemaCheckConstraints, sqlbase.InformationSchemaColumnPrivilegesID: informationSchemaColumnPrivileges, + sqlbase.InformationSchemaColumnStatisticsTableID: informationSchemaColumnStatisticsTable, sqlbase.InformationSchemaColumnsTableID: informationSchemaColumnsTable, sqlbase.InformationSchemaConstraintColumnUsageTableID: informationSchemaConstraintColumnUsageTable, sqlbase.InformationSchemaEnabledRolesID: informationSchemaEnabledRoles, @@ -1356,6 +1360,181 @@ func dStringForIndexDirection(dir sqlbase.IndexDescriptor_Direction) tree.Datum panic("unreachable") } +// columnStatisticsDataType maps KWDB type families to the type categories +// required by the MySQL-compatible histogram JSON representation. +func columnStatisticsDataType(typ *types.T) string { + switch typ.Family() { + case types.BoolFamily, types.IntFamily: + return "int" + case types.FloatFamily: + return "double" + case types.DecimalFamily: + return "decimal" + case types.DateFamily, types.TimestampFamily, types.TimestampTZFamily, + types.TimeFamily, types.TimeTZFamily: + return "datetime" + default: + return "string" + } +} + +// makeColumnStatisticsHistogram converts a KWDB histogram to the +// MySQL-compatible JSON representation used by column_statistics. Singleton +// buckets contain a value and cumulative frequency; equi-height buckets also +// contain display bounds and an estimated distinct-value count. KWDB stores +// only upper bounds. Its first bucket has no range values, so its upper bound +// is also the histogram lower bound; later buckets use the preceding upper +// bound as their display lower bound. +func makeColumnStatisticsHistogram(stat *stats.TableStatistic) (tree.Datum, error) { + histogram := stat.Histogram + if len(histogram) == 0 { + return nil, errors.AssertionFailedf("statistic %d has no histogram buckets", stat.StatisticID) + } + + histogramType := "singleton" + for i := range histogram { + if histogram[i].NumRange != 0 || histogram[i].DistinctRange != 0 { + histogramType = "equi-height" + break + } + } + + nonNullRows := stat.RowCount + if stat.NullCount < nonNullRows { + nonNullRows -= stat.NullCount + } else { + nonNullRows = 0 + } + cumulativeRows := float64(0) + buckets := json.NewArrayBuilder(len(histogram)) + var previousUpperBound json.JSON + for i := range histogram { + bucket := &histogram[i] + upperBound, err := tree.AsJSON(bucket.UpperBound, time.UTC) + if err != nil { + return nil, err + } + + cumulativeRows += float64(bucket.NumRange + bucket.NumEq) + cumulativeFrequency := float64(0) + if nonNullRows > 0 { + cumulativeFrequency = cumulativeRows / float64(nonNullRows) + if cumulativeFrequency > 1 { + cumulativeFrequency = 1 + } + } + frequencyJSON, err := json.FromFloat64(cumulativeFrequency) + if err != nil { + return nil, err + } + + bucketJSON := json.NewArrayBuilder(4) + if histogramType == "singleton" { + bucketJSON.Add(upperBound) + bucketJSON.Add(frequencyJSON) + } else { + // KWDB stores only bucket upper bounds. Reuse the preceding boundary as + // the lower display bound while preserving KWDB's cumulative estimates. + lowerBound := upperBound + if previousUpperBound != nil { + lowerBound = previousUpperBound + } + bucketJSON.Add(lowerBound) + bucketJSON.Add(upperBound) + bucketJSON.Add(frequencyJSON) + + distinctValues := bucket.DistinctRange + if bucket.NumEq > 0 { + distinctValues++ + } + distinctJSON, err := json.FromFloat64(distinctValues) + if err != nil { + return nil, err + } + bucketJSON.Add(distinctJSON) + } + buckets.Add(bucketJSON.Build()) + previousUpperBound = upperBound + } + + nullFraction := float64(0) + if stat.RowCount > 0 { + nullFraction = float64(stat.NullCount) / float64(stat.RowCount) + if nullFraction > 1 { + nullFraction = 1 + } + } + nullFractionJSON, err := json.FromFloat64(nullFraction) + if err != nil { + return nil, err + } + + result := json.NewObjectBuilder(6) + result.Add("buckets", buckets.Build()) + result.Add("data-type", json.FromString(columnStatisticsDataType(&stat.HistogramData.ColumnType))) + result.Add("histogram-type", json.FromString(histogramType)) + result.Add("last-updated", json.FromString(stat.CreatedAt.UTC().Format("2006-01-02 15:04:05.000000"))) + result.Add("null-values", nullFractionJSON) + result.Add("number-of-buckets-specified", json.FromInt(len(histogram))) + return tree.NewDJSON(result.Build()), nil +} + +// MySQL: https://dev.mysql.com/doc/refman/8.0/en/information-schema-column-statistics-table.html +// ISO and PostgreSQL: missing. +var informationSchemaColumnStatisticsTable = virtualSchemaTable{ + comment: `histogram statistics for visible table columns`, + schema: ` +CREATE TABLE information_schema.column_statistics ( + SCHEMA_NAME STRING NOT NULL, + TABLE_NAME STRING NOT NULL, + COLUMN_NAME STRING NOT NULL, + HISTOGRAM JSONB NOT NULL +)`, + populate: func(ctx context.Context, p *planner, dbContext *DatabaseDescriptor, addRow func(...tree.Datum) error) error { + return forEachTableDesc(ctx, p, dbContext, hideVirtual, + func(_ *sqlbase.DatabaseDescriptor, scName string, table *sqlbase.TableDescriptor) error { + tableStats, err := p.ExtendedEvalContext().ExecCfg.TableStatsCache.GetTableStats(ctx, table.ID) + if err != nil { + return err + } + + // GetTableStats returns newest records first. Emit at most one usable + // single-column histogram for each visible column. + emitted := make(map[sqlbase.ColumnID]struct{}) + for _, stat := range tableStats { + if len(stat.ColumnIDs) != 1 || stat.HistogramData == nil || len(stat.Histogram) == 0 { + continue + } + columnID := stat.ColumnIDs[0] + if _, ok := emitted[columnID]; ok { + continue + } + column, err := table.FindColumnByID(columnID) + if err != nil { + return err + } + if column.Hidden { + continue + } + histogram, err := makeColumnStatisticsHistogram(stat) + if err != nil { + return err + } + if err := addRow( + tree.NewDString(scName), // schema_name + tree.NewDString(table.Name), // table_name + tree.NewDString(column.Name), // column_name + histogram, // histogram + ); err != nil { + return err + } + emitted[columnID] = struct{}{} + } + return nil + }) + }, +} + var informationSchemaSequences = virtualSchemaTable{ comment: `sequences ` + base.DocsURL("information-schema.html#sequences") + ` diff --git a/qa/Integration/basic_v3/information_schema_views_04.sql b/qa/Integration/basic_v3/information_schema_views_04.sql new file mode 100644 index 0000000000..916d73eefe --- /dev/null +++ b/qa/Integration/basic_v3/information_schema_views_04.sql @@ -0,0 +1,128 @@ +-- Root-scope regression coverage for the system views added in batch 04. +-- Master files are generated by the integration environment. + +USE defaultdb; +DROP DATABASE IF EXISTS is_views_04 CASCADE; +CREATE DATABASE is_views_04; +SET DATABASE = is_views_04; +CREATE SCHEMA is_views_04_schema; + +-- user_attributes: root can inspect its account, while KWDB has no source for +-- MySQL host-qualified identities or JSON account attributes. +SELECT "user", + host IS NULL AS host_is_null, + attribute IS NULL AS attribute_is_null +FROM information_schema.user_attributes +WHERE "user" = 'root'; + +-- partitions: cover the supported LIST, RANGE, and HASH representations. +CREATE TABLE is_views_04_schema.list_partition_table ( + region INT, + id INT, + PRIMARY KEY (region, id) +) PARTITION BY LIST (region) ( + PARTITION p_east VALUES IN (1), + PARTITION p_west VALUES IN (2) +); + +CREATE TABLE is_views_04_schema.range_partition_table ( + id INT PRIMARY KEY +) PARTITION BY RANGE (id) ( + PARTITION p_low VALUES FROM (MINVALUE) TO (10), + PARTITION p_high VALUES FROM (10) TO (MAXVALUE) +); + +CREATE TABLE is_views_04_schema.hash_partition_table ( + bucket INT PRIMARY KEY +) PARTITION BY HASH (bucket) ( + PARTITION p_hash_0 VALUES IN (0), + PARTITION p_hash_1 VALUES IN (1) +); + +SELECT table_catalog, table_schema, table_name, + partition_name, subpartition_name, + partition_ordinal_position, subpartition_ordinal_position, + partition_method, subpartition_method, + partition_expression, subpartition_expression, + partition_description +FROM kwdb_internal.partitions +WHERE table_catalog = 'is_views_04' + AND table_schema = 'is_views_04_schema' + AND table_name IN ( + 'list_partition_table', + 'range_partition_table', + 'hash_partition_table' + ) +ORDER BY table_name, partition_ordinal_position; + +SELECT table_name, + partition_description = + coalesce(list_value, range_value, hash_value) + AS description_matches_legacy_value, + table_rows IS NULL + AND avg_row_length IS NULL + AND data_length IS NULL + AND max_data_length IS NULL + AND index_length IS NULL + AND data_free IS NULL + AS size_fields_are_null, + create_time IS NULL + AND update_time IS NULL + AND check_time IS NULL + AND checksum IS NULL + AND partition_comment IS NULL + AND nodegroup IS NULL + AND tablespace_name IS NULL + AS unsupported_fields_are_null +FROM kwdb_internal.partitions +WHERE table_catalog = 'is_views_04' + AND table_schema = 'is_views_04_schema' +ORDER BY table_name, partition_ordinal_position; + +-- column_statistics: single-column statistics produce one JSON histogram row; +-- the multi-column statistic must not create an additional COLUMN_NAME row. +CREATE TABLE is_views_04_schema.statistics_table ( + id INT PRIMARY KEY, + value INT, + category STRING +); + +INSERT INTO is_views_04_schema.statistics_table +VALUES + (1, 10, 'a'), + (2, 10, 'a'), + (3, 20, 'b'), + (4, 30, 'c'), + (5, NULL, 'c'); + +CREATE STATISTICS is_views_04_value_stats +ON value +FROM is_views_04_schema.statistics_table; + +CREATE STATISTICS is_views_04_multi_stats +ON value, category +FROM is_views_04_schema.statistics_table; + +SELECT schema_name, table_name, column_name, + histogram->>'histogram-type' AS histogram_type, + (histogram->>'number-of-buckets-specified')::INT AS bucket_count, + (histogram->>'null-values')::DECIMAL = 0.2 AS null_fraction_matches, + histogram->>'data-type' AS data_type +FROM information_schema.column_statistics +WHERE schema_name = 'is_views_04_schema' + AND table_name = 'statistics_table' +ORDER BY column_name; + +-- processlist is dynamic, so assert only stable properties of the root query +-- that reads the view instead of materializing IDs, elapsed time, or SQL text. +SELECT count(*) > 0 AS has_current_root_query +FROM information_schema.processlist +WHERE "user" = 'root' + AND length(id) = 32 + AND command = 'Query' + AND "time" >= 0 + AND state IN ('PREPARING', 'EXECUTING') + AND info LIKE '%information_schema.processlist%'; + +USE defaultdb; +DROP DATABASE is_views_04 CASCADE; diff --git a/qa/Integration/basic_v3/master/ts_create_bug.sql_1n.master b/qa/Integration/basic_v3/master/ts_create_bug.sql_1n.master index 878e6cc6ea..3c8677e5d3 100644 --- a/qa/Integration/basic_v3/master/ts_create_bug.sql_1n.master +++ b/qa/Integration/basic_v3/master/ts_create_bug.sql_1n.master @@ -355,7 +355,7 @@ SET > select count(*) from pg_catalog.pg_class; count --------- - 127 + 128 (1 row) > use defaultdb; SET @@ -366,7 +366,7 @@ SET > select count(*) from pg_catalog.pg_class; count --------- - 127 + 128 (1 row) > use defaultdb; SET diff --git a/qa/Integration/basic_v3/master/ts_create_bug.sql_5c.master b/qa/Integration/basic_v3/master/ts_create_bug.sql_5c.master index 878e6cc6ea..3c8677e5d3 100644 --- a/qa/Integration/basic_v3/master/ts_create_bug.sql_5c.master +++ b/qa/Integration/basic_v3/master/ts_create_bug.sql_5c.master @@ -355,7 +355,7 @@ SET > select count(*) from pg_catalog.pg_class; count --------- - 127 + 128 (1 row) > use defaultdb; SET @@ -366,7 +366,7 @@ SET > select count(*) from pg_catalog.pg_class; count --------- - 127 + 128 (1 row) > use defaultdb; SET diff --git a/qa/Integration/basic_v3/master/ts_create_bug.sql_5cr.master b/qa/Integration/basic_v3/master/ts_create_bug.sql_5cr.master index 878e6cc6ea..3c8677e5d3 100644 --- a/qa/Integration/basic_v3/master/ts_create_bug.sql_5cr.master +++ b/qa/Integration/basic_v3/master/ts_create_bug.sql_5cr.master @@ -355,7 +355,7 @@ SET > select count(*) from pg_catalog.pg_class; count --------- - 127 + 128 (1 row) > use defaultdb; SET @@ -366,7 +366,7 @@ SET > select count(*) from pg_catalog.pg_class; count --------- - 127 + 128 (1 row) > use defaultdb; SET -- Gitee From 000f60939366f3a512f600f4384f407f07dd88c0 Mon Sep 17 00:00:00 2001 From: Yilin Date: Wed, 30 Sep 2026 22:34:17 +0800 Subject: [PATCH 06/18] test: expand partitions metadata coverage --- .../testdata/logic_test/kwdb_internal | 74 +++++++++++++++++++ .../basic_v3/information_schema_views_04.sql | 4 + 2 files changed, 78 insertions(+) diff --git a/kwbase/pkg/sql/logictest/testdata/logic_test/kwdb_internal b/kwbase/pkg/sql/logictest/testdata/logic_test/kwdb_internal index 0d1259cd0b..7c4b9a664e 100644 --- a/kwbase/pkg/sql/logictest/testdata/logic_test/kwdb_internal +++ b/kwbase/pkg/sql/logictest/testdata/logic_test/kwdb_internal @@ -636,6 +636,60 @@ user root statement ok CREATE DATABASE partition_view_db +statement ok +SET DATABASE = partition_view_db + +query TTBTTTBB colnames +SHOW COLUMNS FROM kwdb_internal.partitions +---- +column_name data_type is_nullable column_default generation_expression indices is_hidden is_tag +table_id INT8 false NULL · {} false false +index_id INT8 false NULL · {} false false +parent_name STRING true NULL · {} false false +name STRING false NULL · {} false false +columns INT8 false NULL · {} false false +column_names STRING true NULL · {} false false +list_value STRING true NULL · {} false false +range_value STRING true NULL · {} false false +hash_value STRING true NULL · {} false false +zone_id INT8 true NULL · {} false false +subzone_id INT8 true NULL · {} false false +table_catalog STRING true NULL · {} false false +table_schema STRING true NULL · {} false false +table_name STRING true NULL · {} false false +partition_name STRING true NULL · {} false false +subpartition_name STRING true NULL · {} false false +partition_ordinal_position STRING true NULL · {} false false +subpartition_ordinal_position STRING true NULL · {} false false +partition_method STRING true NULL · {} false false +subpartition_method STRING true NULL · {} false false +partition_expression STRING true NULL · {} false false +subpartition_expression STRING true NULL · {} false false +partition_description STRING true NULL · {} false false +table_rows INT8 true NULL · {} false false +avg_row_length INT8 true NULL · {} false false +data_length INT8 true NULL · {} false false +max_data_length INT8 true NULL · {} false false +index_length INT8 true NULL · {} false false +data_free INT8 true NULL · {} false false +create_time TIMESTAMP true NULL · {} false false +update_time TIMESTAMP true NULL · {} false false +check_time TIMESTAMP true NULL · {} false false +checksum STRING true NULL · {} false false +partition_comment STRING true NULL · {} false false +nodegroup STRING true NULL · {} false false +tablespace_name STRING true NULL · {} false false + +statement ok +CREATE TABLE partition_view_db.ordinary_table (id INT PRIMARY KEY) + +query I +SELECT count(*) + FROM partition_view_db.kwdb_internal.partitions + WHERE table_name = 'ordinary_table' +---- +0 + statement ok CREATE TABLE partition_view_db.partitioned_table ( region INT, @@ -662,6 +716,26 @@ CREATE TABLE partition_view_db.hash_partitioned_table ( PARTITION p_hash_1 VALUES IN (1) ) +statement ok +CREATE TABLE partition_view_db.multi_column_partitioned_table ( + region STRING, + id INT, + PRIMARY KEY (region, id) +) PARTITION BY LIST (region, id) ( + PARTITION p_pair_1 VALUES IN (('east', 1)), + PARTITION p_pair_2 VALUES IN (('west', 2)) +) + +query ITT rowsort +SELECT columns, + column_names, + partition_expression + FROM partition_view_db.kwdb_internal.partitions + WHERE table_name = 'multi_column_partitioned_table' +---- +2 region, id region, id +2 region, id region, id + # Verify that the legacy columns remain populated while the appended columns # expose the same partition nodes using the MySQL-compatible representation. query BBTITBBTIB rowsort diff --git a/qa/Integration/basic_v3/information_schema_views_04.sql b/qa/Integration/basic_v3/information_schema_views_04.sql index 916d73eefe..4bdb8197d1 100644 --- a/qa/Integration/basic_v3/information_schema_views_04.sql +++ b/qa/Integration/basic_v3/information_schema_views_04.sql @@ -39,6 +39,10 @@ CREATE TABLE is_views_04_schema.hash_partition_table ( PARTITION p_hash_1 VALUES IN (1) ); +-- Exercise the public SHOW path that delegates through the legacy partition +-- columns. Its zone configuration text is captured by the generated masters. +SHOW PARTITIONS FROM TABLE is_views_04_schema.list_partition_table; + SELECT table_catalog, table_schema, table_name, partition_name, subpartition_name, partition_ordinal_position, subpartition_ordinal_position, -- Gitee From 0a17be84fcb950b6890da58a802f746d1e5904a2 Mon Sep 17 00:00:00 2001 From: Yilin Date: Thu, 1 Oct 2026 21:23:18 +0800 Subject: [PATCH 07/18] fix: quote user attributes column name --- kwbase/pkg/sql/information_schema.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kwbase/pkg/sql/information_schema.go b/kwbase/pkg/sql/information_schema.go index 23ac0e4d89..ece48f3b15 100644 --- a/kwbase/pkg/sql/information_schema.go +++ b/kwbase/pkg/sql/information_schema.go @@ -1832,7 +1832,7 @@ var informationSchemaUserAttributes = virtualSchemaTable{ comment: `user account attributes (partial MySQL compatibility)`, schema: ` CREATE TABLE information_schema.user_attributes ( - USER STRING NOT NULL, + "USER" STRING NOT NULL, HOST STRING, ATTRIBUTE JSONB )`, -- Gitee From 947d8ba129c4ee7af744ad1d8dd2254f089e641c Mon Sep 17 00:00:00 2001 From: Yilin Date: Fri, 2 Oct 2026 14:22:16 +0800 Subject: [PATCH 08/18] fix: quote processlist user column --- kwbase/pkg/sql/information_schema.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kwbase/pkg/sql/information_schema.go b/kwbase/pkg/sql/information_schema.go index ece48f3b15..3f4feff84e 100644 --- a/kwbase/pkg/sql/information_schema.go +++ b/kwbase/pkg/sql/information_schema.go @@ -858,7 +858,7 @@ var informationSchemaProcesslistTable = virtualSchemaTable{ schema: ` CREATE TABLE information_schema.processlist ( ID STRING, - USER STRING, + "USER" STRING, HOST STRING, DB STRING, COMMAND STRING, -- Gitee From 77a9015b5717a45331900b90cef0da278c7d29c8 Mon Sep 17 00:00:00 2001 From: Yilin Date: Fri, 2 Oct 2026 14:30:44 +0800 Subject: [PATCH 09/18] fix: quote user attributes field name --- kwbase/pkg/sql/information_schema.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kwbase/pkg/sql/information_schema.go b/kwbase/pkg/sql/information_schema.go index 3f4feff84e..8e240e0185 100644 --- a/kwbase/pkg/sql/information_schema.go +++ b/kwbase/pkg/sql/information_schema.go @@ -1834,7 +1834,7 @@ var informationSchemaUserAttributes = virtualSchemaTable{ CREATE TABLE information_schema.user_attributes ( "USER" STRING NOT NULL, HOST STRING, - ATTRIBUTE JSONB + "ATTRIBUTE" JSONB )`, populate: func(ctx context.Context, p *planner, _ *DatabaseDescriptor, addRow func(...tree.Datum) error) error { currentUser := p.SessionData().User -- Gitee From cc3f01d2a1644953b31a3c56d6e8daf3e894f6d9 Mon Sep 17 00:00:00 2001 From: Yilin Date: Fri, 2 Oct 2026 14:41:38 +0800 Subject: [PATCH 10/18] test: fix information schema table order --- kwbase/pkg/sql/logictest/testdata/logic_test/information_schema | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema b/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema index b860aaf1c2..f2b35684cf 100644 --- a/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema +++ b/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema @@ -26,8 +26,8 @@ enabled_roles SYSTEM VIEW key_column_usage SYSTEM VIEW keywords SYSTEM VIEW parameters SYSTEM VIEW -processlist SYSTEM VIEW procedure_privileges SYSTEM VIEW +processlist SYSTEM VIEW referential_constraints SYSTEM VIEW role_column_grants SYSTEM VIEW role_routine_grants SYSTEM VIEW -- Gitee From 57121075e2f1d29cf4e4e11a9d09e3c0668721b2 Mon Sep 17 00:00:00 2001 From: Yilin Date: Fri, 2 Oct 2026 14:45:26 +0800 Subject: [PATCH 11/18] test: fix information schema reflection order --- kwbase/pkg/sql/logictest/testdata/logic_test/information_schema | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema b/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema index f2b35684cf..62f7803bf2 100644 --- a/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema +++ b/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema @@ -142,8 +142,8 @@ enabled_roles SYSTEM VIEW key_column_usage SYSTEM VIEW keywords SYSTEM VIEW parameters SYSTEM VIEW -processlist SYSTEM VIEW procedure_privileges SYSTEM VIEW +processlist SYSTEM VIEW referential_constraints SYSTEM VIEW role_column_grants SYSTEM VIEW role_routine_grants SYSTEM VIEW -- Gitee From fc76636222814d684bdd479898d6badb5f21271b Mon Sep 17 00:00:00 2001 From: Yilin Date: Fri, 2 Oct 2026 14:50:48 +0800 Subject: [PATCH 12/18] test: fix information schema metadata order --- kwbase/pkg/sql/logictest/testdata/logic_test/information_schema | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema b/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema index 62f7803bf2..072c6a9518 100644 --- a/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema +++ b/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema @@ -632,8 +632,8 @@ system information_schema enabled_roles SYSTEM VIE system information_schema key_column_usage SYSTEM VIEW NO 1 3819883323 NULL NULL NULL NULL NULL NO NULL NULL NULL NULL NULL system information_schema keywords SYSTEM VIEW NO 1 3819883323 NULL NULL NULL NULL NULL NO NULL NULL NULL NULL NULL system information_schema parameters SYSTEM VIEW NO 1 3819883323 NULL NULL NULL NULL NULL NO NULL NULL NULL NULL NULL -system information_schema processlist SYSTEM VIEW NO 1 3819883323 NULL NULL NULL NULL NULL NO NULL NULL NULL NULL NULL system information_schema procedure_privileges SYSTEM VIEW NO 1 3819883323 NULL NULL NULL NULL NULL NO NULL NULL NULL NULL NULL +system information_schema processlist SYSTEM VIEW NO 1 3819883323 NULL NULL NULL NULL NULL NO NULL NULL NULL NULL NULL system information_schema referential_constraints SYSTEM VIEW NO 1 3819883323 NULL NULL NULL NULL NULL NO NULL NULL NULL NULL NULL system information_schema role_column_grants SYSTEM VIEW NO 1 3819883323 NULL NULL NULL NULL NULL NO NULL NULL NULL NULL NULL system information_schema role_routine_grants SYSTEM VIEW NO 1 3819883323 NULL NULL NULL NULL NULL NO NULL NULL NULL NULL NULL -- Gitee From 6ae36cba67ee44846df000ca50cf96e52abecd39 Mon Sep 17 00:00:00 2001 From: Yilin Date: Fri, 2 Oct 2026 14:58:23 +0800 Subject: [PATCH 13/18] test: fix table privilege order --- kwbase/pkg/sql/logictest/testdata/logic_test/information_schema | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema b/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema index 072c6a9518..9ebc185f1f 100644 --- a/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema +++ b/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema @@ -1990,8 +1990,8 @@ NULL public system information_schema enabled_roles NULL public system information_schema key_column_usage SELECT NULL NO NULL public system information_schema keywords SELECT NULL NO NULL public system information_schema parameters SELECT NULL NO -NULL public system information_schema processlist SELECT NULL NO NULL public system information_schema procedure_privileges SELECT NULL NO +NULL public system information_schema processlist SELECT NULL NO NULL public system information_schema referential_constraints SELECT NULL NO NULL public system information_schema role_column_grants SELECT NULL NO NULL public system information_schema role_routine_grants SELECT NULL NO -- Gitee From 81dbf3f61efa4df90953b06f6148ad3f417d0507 Mon Sep 17 00:00:00 2001 From: Yilin Date: Fri, 2 Oct 2026 15:05:59 +0800 Subject: [PATCH 14/18] test: align processlist column casing --- kwbase/pkg/sql/logictest/testdata/logic_test/information_schema | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema b/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema index 9ebc185f1f..fe8e750b7e 100644 --- a/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema +++ b/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema @@ -2918,7 +2918,7 @@ SHOW COLUMNS FROM information_schema.processlist ---- column_name data_type is_nullable column_default generation_expression indices is_hidden is_tag id STRING true NULL · {} false false -user STRING true NULL · {} false false +USER STRING true NULL · {} false false host STRING true NULL · {} false false db STRING true NULL · {} false false command STRING true NULL · {} false false -- Gitee From 835ecd9c7e8791754a39151e316b3644abdc2154 Mon Sep 17 00:00:00 2001 From: Yilin Date: Fri, 2 Oct 2026 15:14:00 +0800 Subject: [PATCH 15/18] test: quote processlist user column --- .../sql/logictest/testdata/logic_test/information_schema | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema b/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema index fe8e750b7e..68cf76e23d 100644 --- a/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema +++ b/kwbase/pkg/sql/logictest/testdata/logic_test/information_schema @@ -2930,7 +2930,7 @@ info STRING true NULL · {} query B SELECT count(*) > 0 FROM information_schema.processlist AS p - WHERE p."user" = 'root' + WHERE p."USER" = 'root' AND length(p.id) = 32 AND p.db IS NULL AND p.command = 'Query' @@ -2946,14 +2946,14 @@ user testuser query I SELECT count(*) FROM information_schema.processlist AS p - WHERE p."user" <> 'testuser' + WHERE p."USER" <> 'testuser' ---- 0 query B SELECT count(*) > 0 FROM information_schema.processlist AS p - WHERE p."user" = 'testuser' + WHERE p."USER" = 'testuser' AND length(p.id) = 32 AND p.command = 'Query' AND p."time" >= 0 @@ -2968,7 +2968,7 @@ user root query B SELECT count(*) > 0 FROM information_schema.processlist AS p - WHERE p."user" = 'testuser' + WHERE p."USER" = 'testuser' AND p.command = 'Sleep' AND p.db IS NULL AND p."time" IS NULL -- Gitee From b131571fcfebf44f1b907344f93d89d77baa32c3 Mon Sep 17 00:00:00 2001 From: Yilin Date: Fri, 2 Oct 2026 15:19:34 +0800 Subject: [PATCH 16/18] test: fix pg description order --- kwbase/pkg/sql/logictest/testdata/logic_test/pg_catalog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kwbase/pkg/sql/logictest/testdata/logic_test/pg_catalog b/kwbase/pkg/sql/logictest/testdata/logic_test/pg_catalog index d6aaad19e8..0593638443 100644 --- a/kwbase/pkg/sql/logictest/testdata/logic_test/pg_catalog +++ b/kwbase/pkg/sql/logictest/testdata/logic_test/pg_catalog @@ -1562,8 +1562,8 @@ objoid classoid objsubid description 4294967242 4294967219 0 column usage by indexes and key constraints 4294967169 4294967219 0 SQL keywords 4294967241 4294967219 0 built-in function parameters (empty - introspection not yet supported) -4294967164 4294967219 0 current SQL sessions visible to the current user (cluster RPC; partial MySQL compatibility) 4294967228 4294967219 0 privileges granted on procedure (incomplete; may contain excess users or roles) +4294967164 4294967219 0 current SQL sessions visible to the current user (cluster RPC; partial MySQL compatibility) 4294967240 4294967219 0 foreign key constraints 4294967168 4294967219 0 column privilege grants to roles available to the current user (incomplete; grantor information unavailable) 4294967167 4294967219 0 privileges granted on procedures and Lua user-defined functions -- Gitee From a2c276e193f8b183129806d741af7496c6970e34 Mon Sep 17 00:00:00 2001 From: Yilin Date: Fri, 2 Oct 2026 15:26:36 +0800 Subject: [PATCH 17/18] test: fix grant table order --- kwbase/pkg/sql/logictest/testdata/logic_test/grant_table | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kwbase/pkg/sql/logictest/testdata/logic_test/grant_table b/kwbase/pkg/sql/logictest/testdata/logic_test/grant_table index 2e70b69ef8..56fbbd0a87 100644 --- a/kwbase/pkg/sql/logictest/testdata/logic_test/grant_table +++ b/kwbase/pkg/sql/logictest/testdata/logic_test/grant_table @@ -44,8 +44,8 @@ test information_schema enabled_roles NULL test information_schema key_column_usage NULL public SELECT test information_schema keywords NULL public SELECT test information_schema parameters NULL public SELECT -test information_schema processlist NULL public SELECT test information_schema procedure_privileges NULL public SELECT +test information_schema processlist NULL public SELECT test information_schema referential_constraints NULL public SELECT test information_schema role_column_grants NULL public SELECT test information_schema role_routine_grants NULL public SELECT -- Gitee From 3763e998496a7684431818b90187a590ca2de64c Mon Sep 17 00:00:00 2001 From: Yilin Date: Fri, 2 Oct 2026 18:52:13 +0800 Subject: [PATCH 18/18] test: update information schema views regression masters --- .../basic_v3/information_schema_views_04.sql | 20 +-- .../information_schema_views_04.sql_1n.master | 163 ++++++++++++++++++ .../information_schema_views_04.sql_5c.master | 163 ++++++++++++++++++ ...information_schema_views_04.sql_5cr.master | 161 +++++++++++++++++ 4 files changed, 495 insertions(+), 12 deletions(-) create mode 100644 qa/Integration/basic_v3/master/information_schema_views_04.sql_1n.master create mode 100644 qa/Integration/basic_v3/master/information_schema_views_04.sql_5c.master create mode 100644 qa/Integration/basic_v3/master/information_schema_views_04.sql_5cr.master diff --git a/qa/Integration/basic_v3/information_schema_views_04.sql b/qa/Integration/basic_v3/information_schema_views_04.sql index 4bdb8197d1..e20d04a6ea 100644 --- a/qa/Integration/basic_v3/information_schema_views_04.sql +++ b/qa/Integration/basic_v3/information_schema_views_04.sql @@ -9,11 +9,11 @@ CREATE SCHEMA is_views_04_schema; -- user_attributes: root can inspect its account, while KWDB has no source for -- MySQL host-qualified identities or JSON account attributes. -SELECT "user", +SELECT "USER", host IS NULL AS host_is_null, - attribute IS NULL AS attribute_is_null + "ATTRIBUTE" IS NULL AS attribute_is_null FROM information_schema.user_attributes -WHERE "user" = 'root'; +WHERE "USER" = 'root'; -- partitions: cover the supported LIST, RANGE, and HASH representations. CREATE TABLE is_views_04_schema.list_partition_table ( @@ -28,8 +28,8 @@ CREATE TABLE is_views_04_schema.list_partition_table ( CREATE TABLE is_views_04_schema.range_partition_table ( id INT PRIMARY KEY ) PARTITION BY RANGE (id) ( - PARTITION p_low VALUES FROM (MINVALUE) TO (10), - PARTITION p_high VALUES FROM (10) TO (MAXVALUE) + PARTITION p_low VALUES FROM (1) TO (10), + PARTITION p_high VALUES FROM (10) TO (20) ); CREATE TABLE is_views_04_schema.hash_partition_table ( @@ -83,8 +83,8 @@ WHERE table_catalog = 'is_views_04' AND table_schema = 'is_views_04_schema' ORDER BY table_name, partition_ordinal_position; --- column_statistics: single-column statistics produce one JSON histogram row; --- the multi-column statistic must not create an additional COLUMN_NAME row. +-- column_statistics: single-column statistics produce one JSON histogram row. +-- Ordinary relational multi-column statistics are not supported in this branch. CREATE TABLE is_views_04_schema.statistics_table ( id INT PRIMARY KEY, value INT, @@ -103,10 +103,6 @@ CREATE STATISTICS is_views_04_value_stats ON value FROM is_views_04_schema.statistics_table; -CREATE STATISTICS is_views_04_multi_stats -ON value, category -FROM is_views_04_schema.statistics_table; - SELECT schema_name, table_name, column_name, histogram->>'histogram-type' AS histogram_type, (histogram->>'number-of-buckets-specified')::INT AS bucket_count, @@ -121,7 +117,7 @@ ORDER BY column_name; -- that reads the view instead of materializing IDs, elapsed time, or SQL text. SELECT count(*) > 0 AS has_current_root_query FROM information_schema.processlist -WHERE "user" = 'root' +WHERE "USER" = 'root' AND length(id) = 32 AND command = 'Query' AND "time" >= 0 diff --git a/qa/Integration/basic_v3/master/information_schema_views_04.sql_1n.master b/qa/Integration/basic_v3/master/information_schema_views_04.sql_1n.master new file mode 100644 index 0000000000..94b3356995 --- /dev/null +++ b/qa/Integration/basic_v3/master/information_schema_views_04.sql_1n.master @@ -0,0 +1,163 @@ +> USE defaultdb; +SET +> DROP DATABASE IF EXISTS is_views_04 CASCADE; +DROP DATABASE +> CREATE DATABASE is_views_04; +CREATE DATABASE +> SET DATABASE = is_views_04; +SET +> CREATE SCHEMA is_views_04_schema; +CREATE SCHEMA +> SELECT "USER", + host IS NULL AS host_is_null, + "ATTRIBUTE" IS NULL AS attribute_is_null +FROM information_schema.user_attributes +WHERE "USER" = 'root'; + USER | host_is_null | attribute_is_null +-------+--------------+-------------------- + root | true | true +(1 row) +> CREATE TABLE is_views_04_schema.list_partition_table ( + region INT, + id INT, + PRIMARY KEY (region, id) +) PARTITION BY LIST (region) ( + PARTITION p_east VALUES IN (1), + PARTITION p_west VALUES IN (2) +); +CREATE TABLE +> CREATE TABLE is_views_04_schema.range_partition_table ( + id INT PRIMARY KEY +) PARTITION BY RANGE (id) ( + PARTITION p_low VALUES FROM (1) TO (10), + PARTITION p_high VALUES FROM (10) TO (20) +); +CREATE TABLE +> CREATE TABLE is_views_04_schema.hash_partition_table ( + bucket INT PRIMARY KEY +) PARTITION BY HASH (bucket) ( + PARTITION p_hash_0 VALUES IN (0), + PARTITION p_hash_1 VALUES IN (1) +); +CREATE TABLE +> SHOW PARTITIONS FROM TABLE is_views_04_schema.list_partition_table; + database_name | table_name | partition_name | parent_partition | column_names | index_name | partition_value | zone_config | full_zone_config +----------------+----------------------+----------------+------------------+--------------+------------------------------+-----------------+-------------+------------------------------- + is_views_04 | list_partition_table | p_east | NULL | region | list_partition_table@primary | (1) | NULL | range_min_bytes = 134217728, + | | | | | | | | range_max_bytes = 536870912, + | | | | | | | | gc.ttlseconds = 60, + | | | | | | | | num_replicas = 1, + | | | | | | | | ts_merge.days = 240h0m0s, + | | | | | | | | constraints = '[]', + | | | | | | | | lease_preferences = '[]' + is_views_04 | list_partition_table | p_west | NULL | region | list_partition_table@primary | (2) | NULL | range_min_bytes = 134217728, + | | | | | | | | range_max_bytes = 536870912, + | | | | | | | | gc.ttlseconds = 60, + | | | | | | | | num_replicas = 1, + | | | | | | | | ts_merge.days = 240h0m0s, + | | | | | | | | constraints = '[]', + | | | | | | | | lease_preferences = '[]' +(2 rows) +> SELECT table_catalog, table_schema, table_name, + partition_name, subpartition_name, + partition_ordinal_position, subpartition_ordinal_position, + partition_method, subpartition_method, + partition_expression, subpartition_expression, + partition_description +FROM kwdb_internal.partitions +WHERE table_catalog = 'is_views_04' + AND table_schema = 'is_views_04_schema' + AND table_name IN ( + 'list_partition_table', + 'range_partition_table', + 'hash_partition_table' + ) +ORDER BY table_name, partition_ordinal_position; + table_catalog | table_schema | table_name | partition_name | subpartition_name | partition_ordinal_position | subpartition_ordinal_position | partition_method | subpartition_method | partition_expression | subpartition_expression | partition_description +----------------+--------------------+-----------------------+----------------+-------------------+----------------------------+-------------------------------+------------------+---------------------+----------------------+-------------------------+------------------------ + is_views_04 | is_views_04_schema | hash_partition_table | p_hash_0 | NULL | 1 | NULL | HASH | NULL | bucket | NULL | (0) + is_views_04 | is_views_04_schema | hash_partition_table | p_hash_1 | NULL | 2 | NULL | HASH | NULL | bucket | NULL | (1) + is_views_04 | is_views_04_schema | list_partition_table | p_east | NULL | 1 | NULL | LIST | NULL | region | NULL | (1) + is_views_04 | is_views_04_schema | list_partition_table | p_west | NULL | 2 | NULL | LIST | NULL | region | NULL | (2) + is_views_04 | is_views_04_schema | range_partition_table | p_low | NULL | 1 | NULL | RANGE | NULL | id | NULL | (1) TO (10) + is_views_04 | is_views_04_schema | range_partition_table | p_high | NULL | 2 | NULL | RANGE | NULL | id | NULL | (10) TO (20) +(6 rows) +> SELECT table_name, + partition_description = + coalesce(list_value, range_value, hash_value) + AS description_matches_legacy_value, + table_rows IS NULL + AND avg_row_length IS NULL + AND data_length IS NULL + AND max_data_length IS NULL + AND index_length IS NULL + AND data_free IS NULL + AS size_fields_are_null, + create_time IS NULL + AND update_time IS NULL + AND check_time IS NULL + AND checksum IS NULL + AND partition_comment IS NULL + AND nodegroup IS NULL + AND tablespace_name IS NULL + AS unsupported_fields_are_null +FROM kwdb_internal.partitions +WHERE table_catalog = 'is_views_04' + AND table_schema = 'is_views_04_schema' +ORDER BY table_name, partition_ordinal_position; + table_name | description_matches_legacy_value | size_fields_are_null | unsupported_fields_are_null +------------------------+----------------------------------+----------------------+------------------------------ + hash_partition_table | true | true | true + hash_partition_table | true | true | true + list_partition_table | true | true | true + list_partition_table | true | true | true + range_partition_table | true | true | true + range_partition_table | true | true | true +(6 rows) +> CREATE TABLE is_views_04_schema.statistics_table ( + id INT PRIMARY KEY, + value INT, + category STRING +); +CREATE TABLE +> INSERT INTO is_views_04_schema.statistics_table +VALUES + (1, 10, 'a'), + (2, 10, 'a'), + (3, 20, 'b'), + (4, 30, 'c'), + (5, NULL, 'c'); +INSERT 5 +> CREATE STATISTICS is_views_04_value_stats +ON value +FROM is_views_04_schema.statistics_table; +CREATE STATISTICS +> SELECT schema_name, table_name, column_name, + histogram->>'histogram-type' AS histogram_type, + (histogram->>'number-of-buckets-specified')::INT AS bucket_count, + (histogram->>'null-values')::DECIMAL = 0.2 AS null_fraction_matches, + histogram->>'data-type' AS data_type +FROM information_schema.column_statistics +WHERE schema_name = 'is_views_04_schema' + AND table_name = 'statistics_table' +ORDER BY column_name; + schema_name | table_name | column_name | histogram_type | bucket_count | null_fraction_matches | data_type +---------------------+------------------+-------------+----------------+--------------+-----------------------+------------ + is_views_04_schema | statistics_table | value | singleton | 3 | true | int +(1 row) +> SELECT count(*) > 0 AS has_current_root_query +FROM information_schema.processlist +WHERE "USER" = 'root' + AND length(id) = 32 + AND command = 'Query' + AND "time" >= 0 + AND state IN ('PREPARING', 'EXECUTING') + AND info LIKE '%information_schema.processlist%'; + has_current_root_query +-------------------------- + true +(1 row) +> USE defaultdb; +SET +> DROP DATABASE is_views_04 CASCADE; +DROP DATABASE diff --git a/qa/Integration/basic_v3/master/information_schema_views_04.sql_5c.master b/qa/Integration/basic_v3/master/information_schema_views_04.sql_5c.master new file mode 100644 index 0000000000..8261c88c04 --- /dev/null +++ b/qa/Integration/basic_v3/master/information_schema_views_04.sql_5c.master @@ -0,0 +1,163 @@ +> USE defaultdb; +SET +> DROP DATABASE IF EXISTS is_views_04 CASCADE; +DROP DATABASE +> CREATE DATABASE is_views_04; +CREATE DATABASE +> SET DATABASE = is_views_04; +SET +> CREATE SCHEMA is_views_04_schema; +CREATE SCHEMA +> SELECT "USER", + host IS NULL AS host_is_null, + "ATTRIBUTE" IS NULL AS attribute_is_null +FROM information_schema.user_attributes +WHERE "USER" = 'root'; + USER | host_is_null | attribute_is_null +-------+--------------+-------------------- + root | true | true +(1 row) +> CREATE TABLE is_views_04_schema.list_partition_table ( + region INT, + id INT, + PRIMARY KEY (region, id) +) PARTITION BY LIST (region) ( + PARTITION p_east VALUES IN (1), + PARTITION p_west VALUES IN (2) +); +CREATE TABLE +> CREATE TABLE is_views_04_schema.range_partition_table ( + id INT PRIMARY KEY +) PARTITION BY RANGE (id) ( + PARTITION p_low VALUES FROM (1) TO (10), + PARTITION p_high VALUES FROM (10) TO (20) +); +CREATE TABLE +> CREATE TABLE is_views_04_schema.hash_partition_table ( + bucket INT PRIMARY KEY +) PARTITION BY HASH (bucket) ( + PARTITION p_hash_0 VALUES IN (0), + PARTITION p_hash_1 VALUES IN (1) +); +CREATE TABLE +> SHOW PARTITIONS FROM TABLE is_views_04_schema.list_partition_table; + database_name | table_name | partition_name | parent_partition | column_names | index_name | partition_value | zone_config | full_zone_config +----------------+----------------------+----------------+------------------+--------------+------------------------------+-----------------+-------------+------------------------------- + is_views_04 | list_partition_table | p_east | NULL | region | list_partition_table@primary | (1) | NULL | range_min_bytes = 134217728, + | | | | | | | | range_max_bytes = 536870912, + | | | | | | | | gc.ttlseconds = 60, + | | | | | | | | num_replicas = 3, + | | | | | | | | ts_merge.days = 240h0m0s, + | | | | | | | | constraints = '[]', + | | | | | | | | lease_preferences = '[]' + is_views_04 | list_partition_table | p_west | NULL | region | list_partition_table@primary | (2) | NULL | range_min_bytes = 134217728, + | | | | | | | | range_max_bytes = 536870912, + | | | | | | | | gc.ttlseconds = 60, + | | | | | | | | num_replicas = 3, + | | | | | | | | ts_merge.days = 240h0m0s, + | | | | | | | | constraints = '[]', + | | | | | | | | lease_preferences = '[]' +(2 rows) +> SELECT table_catalog, table_schema, table_name, + partition_name, subpartition_name, + partition_ordinal_position, subpartition_ordinal_position, + partition_method, subpartition_method, + partition_expression, subpartition_expression, + partition_description +FROM kwdb_internal.partitions +WHERE table_catalog = 'is_views_04' + AND table_schema = 'is_views_04_schema' + AND table_name IN ( + 'list_partition_table', + 'range_partition_table', + 'hash_partition_table' + ) +ORDER BY table_name, partition_ordinal_position; + table_catalog | table_schema | table_name | partition_name | subpartition_name | partition_ordinal_position | subpartition_ordinal_position | partition_method | subpartition_method | partition_expression | subpartition_expression | partition_description +----------------+--------------------+-----------------------+----------------+-------------------+----------------------------+-------------------------------+------------------+---------------------+----------------------+-------------------------+------------------------ + is_views_04 | is_views_04_schema | hash_partition_table | p_hash_0 | NULL | 1 | NULL | HASH | NULL | bucket | NULL | (0) + is_views_04 | is_views_04_schema | hash_partition_table | p_hash_1 | NULL | 2 | NULL | HASH | NULL | bucket | NULL | (1) + is_views_04 | is_views_04_schema | list_partition_table | p_east | NULL | 1 | NULL | LIST | NULL | region | NULL | (1) + is_views_04 | is_views_04_schema | list_partition_table | p_west | NULL | 2 | NULL | LIST | NULL | region | NULL | (2) + is_views_04 | is_views_04_schema | range_partition_table | p_low | NULL | 1 | NULL | RANGE | NULL | id | NULL | (1) TO (10) + is_views_04 | is_views_04_schema | range_partition_table | p_high | NULL | 2 | NULL | RANGE | NULL | id | NULL | (10) TO (20) +(6 rows) +> SELECT table_name, + partition_description = + coalesce(list_value, range_value, hash_value) + AS description_matches_legacy_value, + table_rows IS NULL + AND avg_row_length IS NULL + AND data_length IS NULL + AND max_data_length IS NULL + AND index_length IS NULL + AND data_free IS NULL + AS size_fields_are_null, + create_time IS NULL + AND update_time IS NULL + AND check_time IS NULL + AND checksum IS NULL + AND partition_comment IS NULL + AND nodegroup IS NULL + AND tablespace_name IS NULL + AS unsupported_fields_are_null +FROM kwdb_internal.partitions +WHERE table_catalog = 'is_views_04' + AND table_schema = 'is_views_04_schema' +ORDER BY table_name, partition_ordinal_position; + table_name | description_matches_legacy_value | size_fields_are_null | unsupported_fields_are_null +------------------------+----------------------------------+----------------------+------------------------------ + hash_partition_table | true | true | true + hash_partition_table | true | true | true + list_partition_table | true | true | true + list_partition_table | true | true | true + range_partition_table | true | true | true + range_partition_table | true | true | true +(6 rows) +> CREATE TABLE is_views_04_schema.statistics_table ( + id INT PRIMARY KEY, + value INT, + category STRING +); +CREATE TABLE +> INSERT INTO is_views_04_schema.statistics_table +VALUES + (1, 10, 'a'), + (2, 10, 'a'), + (3, 20, 'b'), + (4, 30, 'c'), + (5, NULL, 'c'); +INSERT 5 +> CREATE STATISTICS is_views_04_value_stats +ON value +FROM is_views_04_schema.statistics_table; +CREATE STATISTICS +> SELECT schema_name, table_name, column_name, + histogram->>'histogram-type' AS histogram_type, + (histogram->>'number-of-buckets-specified')::INT AS bucket_count, + (histogram->>'null-values')::DECIMAL = 0.2 AS null_fraction_matches, + histogram->>'data-type' AS data_type +FROM information_schema.column_statistics +WHERE schema_name = 'is_views_04_schema' + AND table_name = 'statistics_table' +ORDER BY column_name; + schema_name | table_name | column_name | histogram_type | bucket_count | null_fraction_matches | data_type +---------------------+------------------+-------------+----------------+--------------+-----------------------+------------ + is_views_04_schema | statistics_table | value | singleton | 3 | true | int +(1 row) +> SELECT count(*) > 0 AS has_current_root_query +FROM information_schema.processlist +WHERE "USER" = 'root' + AND length(id) = 32 + AND command = 'Query' + AND "time" >= 0 + AND state IN ('PREPARING', 'EXECUTING') + AND info LIKE '%information_schema.processlist%'; + has_current_root_query +-------------------------- + true +(1 row) +> USE defaultdb; +SET +> DROP DATABASE is_views_04 CASCADE; +DROP DATABASE diff --git a/qa/Integration/basic_v3/master/information_schema_views_04.sql_5cr.master b/qa/Integration/basic_v3/master/information_schema_views_04.sql_5cr.master new file mode 100644 index 0000000000..06622fd4e6 --- /dev/null +++ b/qa/Integration/basic_v3/master/information_schema_views_04.sql_5cr.master @@ -0,0 +1,161 @@ +> USE defaultdb; +SET +> DROP DATABASE IF EXISTS is_views_04 CASCADE; +DROP DATABASE +> CREATE DATABASE is_views_04; +CREATE DATABASE +> SET DATABASE = is_views_04; +SET +> CREATE SCHEMA is_views_04_schema; +CREATE SCHEMA +> SELECT "USER", + host IS NULL AS host_is_null, + "ATTRIBUTE" IS NULL AS attribute_is_null +FROM information_schema.user_attributes +WHERE "USER" = 'root'; + USER | host_is_null | attribute_is_null +-------+--------------+-------------------- + root | true | true +(1 row) +> CREATE TABLE is_views_04_schema.list_partition_table ( + region INT, + id INT, + PRIMARY KEY (region, id) +) PARTITION BY LIST (region) ( + PARTITION p_east VALUES IN (1), + PARTITION p_west VALUES IN (2) +); +CREATE TABLE +> CREATE TABLE is_views_04_schema.range_partition_table ( + id INT PRIMARY KEY +) PARTITION BY RANGE (id) ( + PARTITION p_low VALUES FROM (1) TO (10), + PARTITION p_high VALUES FROM (10) TO (20) +); +CREATE TABLE +> CREATE TABLE is_views_04_schema.hash_partition_table ( + bucket INT PRIMARY KEY +) PARTITION BY HASH (bucket) ( + PARTITION p_hash_0 VALUES IN (0), + PARTITION p_hash_1 VALUES IN (1) +); +CREATE TABLE +> SHOW PARTITIONS FROM TABLE is_views_04_schema.list_partition_table; + database_name | table_name | partition_name | parent_partition | column_names | index_name | partition_value | zone_config | full_zone_config +----------------+----------------------+----------------+------------------+--------------+------------------------------+-----------------+-------------+------------------------------- + is_views_04 | list_partition_table | p_east | NULL | region | list_partition_table@primary | (1) | NULL | range_min_bytes = 134217728, + | | | | | | | | range_max_bytes = 536870912, + | | | | | | | | gc.ttlseconds = 60, + | | | | | | | | num_replicas = 1, + | | | | | | | | constraints = '[]', + | | | | | | | | lease_preferences = '[]' + is_views_04 | list_partition_table | p_west | NULL | region | list_partition_table@primary | (2) | NULL | range_min_bytes = 134217728, + | | | | | | | | range_max_bytes = 536870912, + | | | | | | | | gc.ttlseconds = 60, + | | | | | | | | num_replicas = 1, + | | | | | | | | constraints = '[]', + | | | | | | | | lease_preferences = '[]' +(2 rows) +> SELECT table_catalog, table_schema, table_name, + partition_name, subpartition_name, + partition_ordinal_position, subpartition_ordinal_position, + partition_method, subpartition_method, + partition_expression, subpartition_expression, + partition_description +FROM kwdb_internal.partitions +WHERE table_catalog = 'is_views_04' + AND table_schema = 'is_views_04_schema' + AND table_name IN ( + 'list_partition_table', + 'range_partition_table', + 'hash_partition_table' + ) +ORDER BY table_name, partition_ordinal_position; + table_catalog | table_schema | table_name | partition_name | subpartition_name | partition_ordinal_position | subpartition_ordinal_position | partition_method | subpartition_method | partition_expression | subpartition_expression | partition_description +----------------+--------------------+-----------------------+----------------+-------------------+----------------------------+-------------------------------+------------------+---------------------+----------------------+-------------------------+------------------------ + is_views_04 | is_views_04_schema | hash_partition_table | p_hash_0 | NULL | 1 | NULL | HASH | NULL | bucket | NULL | (0) + is_views_04 | is_views_04_schema | hash_partition_table | p_hash_1 | NULL | 2 | NULL | HASH | NULL | bucket | NULL | (1) + is_views_04 | is_views_04_schema | list_partition_table | p_east | NULL | 1 | NULL | LIST | NULL | region | NULL | (1) + is_views_04 | is_views_04_schema | list_partition_table | p_west | NULL | 2 | NULL | LIST | NULL | region | NULL | (2) + is_views_04 | is_views_04_schema | range_partition_table | p_low | NULL | 1 | NULL | RANGE | NULL | id | NULL | (1) TO (10) + is_views_04 | is_views_04_schema | range_partition_table | p_high | NULL | 2 | NULL | RANGE | NULL | id | NULL | (10) TO (20) +(6 rows) +> SELECT table_name, + partition_description = + coalesce(list_value, range_value, hash_value) + AS description_matches_legacy_value, + table_rows IS NULL + AND avg_row_length IS NULL + AND data_length IS NULL + AND max_data_length IS NULL + AND index_length IS NULL + AND data_free IS NULL + AS size_fields_are_null, + create_time IS NULL + AND update_time IS NULL + AND check_time IS NULL + AND checksum IS NULL + AND partition_comment IS NULL + AND nodegroup IS NULL + AND tablespace_name IS NULL + AS unsupported_fields_are_null +FROM kwdb_internal.partitions +WHERE table_catalog = 'is_views_04' + AND table_schema = 'is_views_04_schema' +ORDER BY table_name, partition_ordinal_position; + table_name | description_matches_legacy_value | size_fields_are_null | unsupported_fields_are_null +------------------------+----------------------------------+----------------------+------------------------------ + hash_partition_table | true | true | true + hash_partition_table | true | true | true + list_partition_table | true | true | true + list_partition_table | true | true | true + range_partition_table | true | true | true + range_partition_table | true | true | true +(6 rows) +> CREATE TABLE is_views_04_schema.statistics_table ( + id INT PRIMARY KEY, + value INT, + category STRING +); +CREATE TABLE +> INSERT INTO is_views_04_schema.statistics_table +VALUES + (1, 10, 'a'), + (2, 10, 'a'), + (3, 20, 'b'), + (4, 30, 'c'), + (5, NULL, 'c'); +INSERT 5 +> CREATE STATISTICS is_views_04_value_stats +ON value +FROM is_views_04_schema.statistics_table; +CREATE STATISTICS +> SELECT schema_name, table_name, column_name, + histogram->>'histogram-type' AS histogram_type, + (histogram->>'number-of-buckets-specified')::INT AS bucket_count, + (histogram->>'null-values')::DECIMAL = 0.2 AS null_fraction_matches, + histogram->>'data-type' AS data_type +FROM information_schema.column_statistics +WHERE schema_name = 'is_views_04_schema' + AND table_name = 'statistics_table' +ORDER BY column_name; + schema_name | table_name | column_name | histogram_type | bucket_count | null_fraction_matches | data_type +---------------------+------------------+-------------+----------------+--------------+-----------------------+------------ + is_views_04_schema | statistics_table | value | singleton | 3 | true | int +(1 row) +> SELECT count(*) > 0 AS has_current_root_query +FROM information_schema.processlist +WHERE "USER" = 'root' + AND length(id) = 32 + AND command = 'Query' + AND "time" >= 0 + AND state IN ('PREPARING', 'EXECUTING') + AND info LIKE '%information_schema.processlist%'; + has_current_root_query +-------------------------- + true +(1 row) +> USE defaultdb; +SET +> DROP DATABASE is_views_04 CASCADE; +DROP DATABASE -- Gitee