package query import ( "context" "database/sql" "errors" "time" sq "github.com/Masterminds/squirrel" "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/call" "github.com/zitadel/zitadel/internal/eventstore/handler/v2" "github.com/zitadel/zitadel/internal/query/projection" "github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/zerrors" ) type OrgMetadataList struct { SearchResponse Metadata []*OrgMetadata } type OrgMetadata struct { CreationDate time.Time ChangeDate time.Time ResourceOwner string Sequence uint64 Key string Value []byte } type OrgMetadataSearchQueries struct { SearchRequest Queries []SearchQuery } var ( orgMetadataTable = table{ name: projection.OrgMetadataProjectionTable, instanceIDCol: projection.OrgMetadataColumnInstanceID, } OrgMetadataOrgIDCol = Column{ name: projection.OrgMetadataColumnOrgID, table: orgMetadataTable, } OrgMetadataCreationDateCol = Column{ name: projection.OrgMetadataColumnCreationDate, table: orgMetadataTable, } OrgMetadataChangeDateCol = Column{ name: projection.OrgMetadataColumnChangeDate, table: orgMetadataTable, } OrgMetadataResourceOwnerCol = Column{ name: projection.OrgMetadataColumnResourceOwner, table: orgMetadataTable, } OrgMetadataInstanceIDCol = Column{ name: projection.OrgMetadataColumnInstanceID, table: orgMetadataTable, } OrgMetadataSequenceCol = Column{ name: projection.OrgMetadataColumnSequence, table: orgMetadataTable, } OrgMetadataKeyCol = Column{ name: projection.OrgMetadataColumnKey, table: orgMetadataTable, } OrgMetadataValueCol = Column{ name: projection.OrgMetadataColumnValue, table: orgMetadataTable, } OrgMetadataOwnerRemovedCol = Column{ name: projection.OrgMetadataColumnOwnerRemoved, table: orgMetadataTable, } ) func (q *Queries) GetOrgMetadataByKey(ctx context.Context, shouldTriggerBulk bool, orgID string, key string, withOwnerRemoved bool, queries ...SearchQuery) (metadata *OrgMetadata, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() if shouldTriggerBulk { _, traceSpan := tracing.NewNamedSpan(ctx, "TriggerOrgMetadataProjection") ctx, err = projection.OrgMetadataProjection.Trigger(ctx, handler.WithAwaitRunning()) logging.OnError(err).Debug("trigger failed") traceSpan.EndWithError(err) } query, scan := prepareOrgMetadataQuery(ctx, q.client) for _, q := range queries { query = q.toQuery(query) } eq := sq.Eq{ OrgMetadataOrgIDCol.identifier(): orgID, OrgMetadataKeyCol.identifier(): key, OrgMetadataInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(), } if !withOwnerRemoved { eq[OrgMetadataOwnerRemovedCol.identifier()] = false } stmt, args, err := query.Where(eq).ToSql() if err != nil { return nil, zerrors.ThrowInternal(err, "QUERY-aDaG2", "Errors.Query.SQLStatment") } err = q.client.QueryRowContext(ctx, func(row *sql.Row) error { metadata, err = scan(row) return err }, stmt, args...) return metadata, err } func (q *Queries) SearchOrgMetadata(ctx context.Context, shouldTriggerBulk bool, orgID string, queries *OrgMetadataSearchQueries, withOwnerRemoved bool) (metadata *OrgMetadataList, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() if shouldTriggerBulk { _, traceSpan := tracing.NewNamedSpan(ctx, "TriggerOrgMetadataProjection") ctx, err = projection.OrgMetadataProjection.Trigger(ctx, handler.WithAwaitRunning()) logging.OnError(err).Debug("trigger failed") traceSpan.EndWithError(err) } eq := sq.Eq{ OrgMetadataOrgIDCol.identifier(): orgID, OrgMetadataInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(), } if !withOwnerRemoved { eq[OrgMetadataOwnerRemovedCol.identifier()] = false } query, scan := prepareOrgMetadataListQuery(ctx, q.client) stmt, args, err := queries.toQuery(query).Where(eq).ToSql() if err != nil { return nil, zerrors.ThrowInternal(err, "QUERY-Egbld", "Errors.Query.SQLStatment") } err = q.client.QueryContext(ctx, func(rows *sql.Rows) error { metadata, err = scan(rows) return err }, stmt, args...) if err != nil { return nil, zerrors.ThrowInternal(err, "QUERY-Ho2wf", "Errors.Internal") } metadata.State, err = q.latestState(ctx, orgMetadataTable) return metadata, err } func (q *OrgMetadataSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder { query = q.SearchRequest.toQuery(query) for _, q := range q.Queries { query = q.toQuery(query) } return query } func (r *OrgMetadataSearchQueries) AppendMyResourceOwnerQuery(orgID string) error { query, err := NewOrgMetadataResourceOwnerSearchQuery(orgID) if err != nil { return err } r.Queries = append(r.Queries, query) return nil } func NewOrgMetadataResourceOwnerSearchQuery(value string) (SearchQuery, error) { return NewTextQuery(OrgMetadataResourceOwnerCol, value, TextEquals) } func NewOrgMetadataKeySearchQuery(value string, comparison TextComparison) (SearchQuery, error) { return NewTextQuery(OrgMetadataKeyCol, value, comparison) } func prepareOrgMetadataQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*OrgMetadata, error)) { return sq.Select( OrgMetadataCreationDateCol.identifier(), OrgMetadataChangeDateCol.identifier(), OrgMetadataResourceOwnerCol.identifier(), OrgMetadataSequenceCol.identifier(), OrgMetadataKeyCol.identifier(), OrgMetadataValueCol.identifier(), ). From(orgMetadataTable.identifier() + db.Timetravel(call.Took(ctx))). PlaceholderFormat(sq.Dollar), func(row *sql.Row) (*OrgMetadata, error) { m := new(OrgMetadata) err := row.Scan( &m.CreationDate, &m.ChangeDate, &m.ResourceOwner, &m.Sequence, &m.Key, &m.Value, ) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, zerrors.ThrowNotFound(err, "QUERY-Rph32", "Errors.Metadata.NotFound") } return nil, zerrors.ThrowInternal(err, "QUERY-Hajt2", "Errors.Internal") } return m, nil } } func prepareOrgMetadataListQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) (*OrgMetadataList, error)) { return sq.Select( OrgMetadataCreationDateCol.identifier(), OrgMetadataChangeDateCol.identifier(), OrgMetadataResourceOwnerCol.identifier(), OrgMetadataSequenceCol.identifier(), OrgMetadataKeyCol.identifier(), OrgMetadataValueCol.identifier(), countColumn.identifier()). From(orgMetadataTable.identifier() + db.Timetravel(call.Took(ctx))). PlaceholderFormat(sq.Dollar), func(rows *sql.Rows) (*OrgMetadataList, error) { metadata := make([]*OrgMetadata, 0) var count uint64 for rows.Next() { m := new(OrgMetadata) err := rows.Scan( &m.CreationDate, &m.ChangeDate, &m.ResourceOwner, &m.Sequence, &m.Key, &m.Value, &count, ) if err != nil { return nil, err } metadata = append(metadata, m) } if err := rows.Close(); err != nil { return nil, zerrors.ThrowInternal(err, "QUERY-dd3gh", "Errors.Query.CloseRows") } return &OrgMetadataList{ Metadata: metadata, SearchResponse: SearchResponse{ Count: count, }, }, nil } }