github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/datastore/mysql/stats.go (about)

     1  package mysql
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"fmt"
     7  
     8  	"github.com/Masterminds/squirrel"
     9  
    10  	"github.com/authzed/spicedb/internal/datastore/common"
    11  	"github.com/authzed/spicedb/pkg/datastore"
    12  )
    13  
    14  const (
    15  	informationSchemaTableRowsColumn = "table_rows"
    16  	informationSchemaTablesTable     = "INFORMATION_SCHEMA.TABLES"
    17  	informationSchemaTableNameColumn = "table_name"
    18  
    19  	metadataIDColumn       = "id"
    20  	metadataUniqueIDColumn = "unique_id"
    21  )
    22  
    23  func (mds *Datastore) Statistics(ctx context.Context) (datastore.Stats, error) {
    24  	if mds.analyzeBeforeStats {
    25  		_, err := mds.db.ExecContext(ctx, "ANALYZE TABLE "+mds.driver.RelationTuple())
    26  		if err != nil {
    27  			return datastore.Stats{}, fmt.Errorf("unable to run ANALYZE TABLE: %w", err)
    28  		}
    29  	}
    30  
    31  	uniqueID, err := mds.getUniqueID(ctx)
    32  	if err != nil {
    33  		return datastore.Stats{}, err
    34  	}
    35  
    36  	query, args, err := sb.
    37  		Select(informationSchemaTableRowsColumn).
    38  		From(informationSchemaTablesTable).
    39  		Where(squirrel.Eq{informationSchemaTableNameColumn: mds.driver.RelationTuple()}).
    40  		ToSql()
    41  	if err != nil {
    42  		return datastore.Stats{}, err
    43  	}
    44  
    45  	var count sql.NullInt64
    46  	err = mds.db.QueryRowContext(ctx, query, args...).Scan(&count)
    47  	if err != nil {
    48  		return datastore.Stats{}, err
    49  	}
    50  
    51  	if !count.Valid || count.Int64 == 0 {
    52  		// If we get a count of zero, its possible the information schema table has not yet
    53  		// been updated, so we use a slower count(*) call.
    54  		query, args, err := mds.QueryBuilder.CountTupleQuery.ToSql()
    55  		if err != nil {
    56  			return datastore.Stats{}, err
    57  		}
    58  		err = mds.db.QueryRowContext(ctx, query, args...).Scan(&count)
    59  		if err != nil {
    60  			return datastore.Stats{}, err
    61  		}
    62  	}
    63  
    64  	nsQuery := mds.ReadNamespaceQuery.Where(squirrel.Eq{colDeletedTxn: liveDeletedTxnID})
    65  
    66  	tx, err := mds.db.BeginTx(ctx, nil)
    67  	if err != nil {
    68  		return datastore.Stats{}, err
    69  	}
    70  	defer common.LogOnError(ctx, tx.Rollback)
    71  
    72  	nsDefs, err := loadAllNamespaces(ctx, tx, nsQuery)
    73  	if err != nil {
    74  		return datastore.Stats{}, fmt.Errorf("unable to load namespaces: %w", err)
    75  	}
    76  
    77  	return datastore.Stats{
    78  		UniqueID:                   uniqueID,
    79  		ObjectTypeStatistics:       datastore.ComputeObjectTypeStats(nsDefs),
    80  		EstimatedRelationshipCount: uint64(count.Int64),
    81  	}, nil
    82  }
    83  
    84  func (mds *Datastore) getUniqueID(ctx context.Context) (string, error) {
    85  	sql, args, err := sb.Select(metadataUniqueIDColumn).From(mds.driver.Metadata()).ToSql()
    86  	if err != nil {
    87  		return "", fmt.Errorf("unable to generate query sql: %w", err)
    88  	}
    89  
    90  	var uniqueID string
    91  	if err := mds.db.QueryRowContext(ctx, sql, args...).Scan(&uniqueID); err != nil {
    92  		return "", fmt.Errorf("unable to query unique ID: %w", err)
    93  	}
    94  
    95  	return uniqueID, nil
    96  }