mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 15:17:33 +00:00
perf(query): reduce user query duration (#10037)
# Which Problems Are Solved The resource usage to query user(s) on the database was high and therefore could have performance impact. # How the Problems Are Solved Database queries involving the users and loginnames table were improved and an index was added for user by email query. # Additional Changes - spellchecks - updated apis on load tests # additional info needs cherry pick to v3
This commit is contained in:
49
cmd/setup/58.go
Normal file
49
cmd/setup/58.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"embed"
|
||||
"fmt"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed 58/*.sql
|
||||
replaceLoginNames3View embed.FS
|
||||
)
|
||||
|
||||
type ReplaceLoginNames3View struct {
|
||||
dbClient *database.DB
|
||||
}
|
||||
|
||||
func (mig *ReplaceLoginNames3View) Execute(ctx context.Context, _ eventstore.Event) error {
|
||||
var exists bool
|
||||
err := mig.dbClient.QueryRowContext(ctx, func(r *sql.Row) error {
|
||||
return r.Scan(&exists)
|
||||
}, "SELECT exists(SELECT 1 from information_schema.views WHERE table_schema = 'projections' AND table_name = 'login_names3')")
|
||||
|
||||
if err != nil || !exists {
|
||||
return err
|
||||
}
|
||||
|
||||
statements, err := readStatements(replaceLoginNames3View, "58")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, stmt := range statements {
|
||||
logging.WithFields("file", stmt.file, "migration", mig.String()).Info("execute statement")
|
||||
if _, err := mig.dbClient.ExecContext(ctx, stmt.query); err != nil {
|
||||
return fmt.Errorf("%s %s: %w", mig.String(), stmt.file, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mig *ReplaceLoginNames3View) String() string {
|
||||
return "58_replace_login_names3_view"
|
||||
}
|
36
cmd/setup/58/01_update_login_names3_view.sql
Normal file
36
cmd/setup/58/01_update_login_names3_view.sql
Normal file
@@ -0,0 +1,36 @@
|
||||
CREATE OR REPLACE VIEW projections.login_names3 AS
|
||||
SELECT
|
||||
u.id AS user_id
|
||||
, CASE
|
||||
WHEN p.must_be_domain THEN CONCAT(u.user_name, '@', d.name)
|
||||
ELSE u.user_name
|
||||
END AS login_name
|
||||
, COALESCE(d.is_primary, TRUE) AS is_primary
|
||||
, u.instance_id
|
||||
FROM
|
||||
projections.login_names3_users AS u
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT
|
||||
must_be_domain
|
||||
, is_default
|
||||
FROM
|
||||
projections.login_names3_policies AS p
|
||||
WHERE
|
||||
(
|
||||
p.instance_id = u.instance_id
|
||||
AND NOT p.is_default
|
||||
AND p.resource_owner = u.resource_owner
|
||||
) OR (
|
||||
p.instance_id = u.instance_id
|
||||
AND p.is_default
|
||||
)
|
||||
ORDER BY
|
||||
p.is_default -- custom first
|
||||
LIMIT 1
|
||||
) AS p ON TRUE
|
||||
LEFT JOIN
|
||||
projections.login_names3_domains d
|
||||
ON
|
||||
p.must_be_domain
|
||||
AND u.resource_owner = d.resource_owner
|
||||
AND u.instance_id = d.instance_id
|
1
cmd/setup/58/02_create_index.sql
Normal file
1
cmd/setup/58/02_create_index.sql
Normal file
@@ -0,0 +1 @@
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS login_names3_policies_is_default_owner_idx ON projections.login_names3_policies (instance_id, is_default, resource_owner) INCLUDE (must_be_domain)
|
@@ -154,6 +154,7 @@ type Steps struct {
|
||||
s55ExecutionHandlerStart *ExecutionHandlerStart
|
||||
s56IDPTemplate6SAMLFederatedLogout *IDPTemplate6SAMLFederatedLogout
|
||||
s57CreateResourceCounts *CreateResourceCounts
|
||||
s58ReplaceLoginNames3View *ReplaceLoginNames3View
|
||||
}
|
||||
|
||||
func MustNewSteps(v *viper.Viper) *Steps {
|
||||
|
@@ -216,6 +216,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
|
||||
steps.s55ExecutionHandlerStart = &ExecutionHandlerStart{dbClient: dbClient}
|
||||
steps.s56IDPTemplate6SAMLFederatedLogout = &IDPTemplate6SAMLFederatedLogout{dbClient: dbClient}
|
||||
steps.s57CreateResourceCounts = &CreateResourceCounts{dbClient: dbClient}
|
||||
steps.s58ReplaceLoginNames3View = &ReplaceLoginNames3View{dbClient: dbClient}
|
||||
|
||||
err = projection.Create(ctx, dbClient, eventstoreClient, config.Projections, nil, nil, nil)
|
||||
logging.OnError(err).Fatal("unable to start projections")
|
||||
@@ -262,6 +263,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
|
||||
steps.s55ExecutionHandlerStart,
|
||||
steps.s56IDPTemplate6SAMLFederatedLogout,
|
||||
steps.s57CreateResourceCounts,
|
||||
steps.s58ReplaceLoginNames3View,
|
||||
} {
|
||||
setupErr = executeMigration(ctx, eventstoreClient, step, "migration failed")
|
||||
if setupErr != nil {
|
||||
|
@@ -84,7 +84,7 @@ func (q *Queries) OIDCSettingsByAggID(ctx context.Context, aggregateID string) (
|
||||
OIDCSettingsColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
}).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-s9nle", "Errors.Query.SQLStatment")
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-s9nle", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
||||
|
@@ -103,7 +103,7 @@ func (q *Queries) GetOrgMetadataByKey(ctx context.Context, shouldTriggerBulk boo
|
||||
}
|
||||
stmt, args, err := query.Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-aDaG2", "Errors.Query.SQLStatment")
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-aDaG2", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
||||
@@ -133,7 +133,7 @@ func (q *Queries) SearchOrgMetadata(ctx context.Context, shouldTriggerBulk bool,
|
||||
query, scan := prepareOrgMetadataListQuery()
|
||||
stmt, args, err := queries.toQuery(query).Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Egbld", "Errors.Query.SQLStatment")
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Egbld", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
err = q.client.QueryContext(ctx, func(rows *sql.Rows) error {
|
||||
|
@@ -211,7 +211,7 @@ func (q *Queries) ProjectByID(ctx context.Context, shouldTriggerBulk bool, id st
|
||||
}
|
||||
query, args, err := stmt.Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-2m00Q", "Errors.Query.SQLStatment")
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-2m00Q", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
||||
|
@@ -167,7 +167,7 @@ func (q *Queries) ProjectGrantByID(ctx context.Context, shouldTriggerBulk bool,
|
||||
}
|
||||
query, args, err := stmt.Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Nf93d", "Errors.Query.SQLStatment")
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Nf93d", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
||||
@@ -189,7 +189,7 @@ func (q *Queries) ProjectGrantByIDAndGrantedOrg(ctx context.Context, id, granted
|
||||
}
|
||||
query, args, err := stmt.Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-MO9fs", "Errors.Query.SQLStatment")
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-MO9fs", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
||||
|
@@ -2,9 +2,7 @@ package projection
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
_ "embed"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
old_handler "github.com/zitadel/zitadel/internal/eventstore/handler"
|
||||
@@ -58,105 +56,8 @@ const (
|
||||
LoginNamePoliciesInstanceIDCol = "instance_id"
|
||||
)
|
||||
|
||||
var (
|
||||
policyUsers = sq.Select(
|
||||
alias(
|
||||
col(usersAlias, LoginNameUserIDCol),
|
||||
LoginNameUserCol,
|
||||
),
|
||||
col(usersAlias, LoginNameUserUserNameCol),
|
||||
col(usersAlias, LoginNameUserInstanceIDCol),
|
||||
col(usersAlias, LoginNameUserResourceOwnerCol),
|
||||
alias(
|
||||
coalesce(col(policyCustomAlias, LoginNamePoliciesMustBeDomainCol), col(policyDefaultAlias, LoginNamePoliciesMustBeDomainCol)),
|
||||
LoginNamePoliciesMustBeDomainCol,
|
||||
),
|
||||
).From(alias(LoginNameUserProjectionTable, usersAlias)).
|
||||
LeftJoin(
|
||||
leftJoin(LoginNamePolicyProjectionTable, policyCustomAlias,
|
||||
eq(col(policyCustomAlias, LoginNamePoliciesResourceOwnerCol), col(usersAlias, LoginNameUserResourceOwnerCol)),
|
||||
eq(col(policyCustomAlias, LoginNamePoliciesInstanceIDCol), col(usersAlias, LoginNameUserInstanceIDCol)),
|
||||
),
|
||||
).
|
||||
LeftJoin(
|
||||
leftJoin(LoginNamePolicyProjectionTable, policyDefaultAlias,
|
||||
eq(col(policyDefaultAlias, LoginNamePoliciesIsDefaultCol), "true"),
|
||||
eq(col(policyDefaultAlias, LoginNamePoliciesInstanceIDCol), col(usersAlias, LoginNameUserInstanceIDCol)),
|
||||
),
|
||||
)
|
||||
|
||||
loginNamesTable = sq.Select(
|
||||
col(policyUsersAlias, LoginNameUserCol),
|
||||
col(policyUsersAlias, LoginNameUserUserNameCol),
|
||||
col(policyUsersAlias, LoginNameUserResourceOwnerCol),
|
||||
alias(col(policyUsersAlias, LoginNameUserInstanceIDCol),
|
||||
LoginNameInstanceIDCol),
|
||||
col(policyUsersAlias, LoginNamePoliciesMustBeDomainCol),
|
||||
alias(col(domainsAlias, LoginNameDomainNameCol),
|
||||
domainAlias),
|
||||
col(domainsAlias, LoginNameDomainIsPrimaryCol),
|
||||
).FromSelect(policyUsers, policyUsersAlias).
|
||||
LeftJoin(
|
||||
leftJoin(LoginNameDomainProjectionTable, domainsAlias,
|
||||
col(policyUsersAlias, LoginNamePoliciesMustBeDomainCol),
|
||||
eq(col(policyUsersAlias, LoginNameUserResourceOwnerCol), col(domainsAlias, LoginNameDomainResourceOwnerCol)),
|
||||
eq(col(policyUsersAlias, LoginNamePoliciesInstanceIDCol), col(domainsAlias, LoginNameDomainInstanceIDCol)),
|
||||
),
|
||||
)
|
||||
|
||||
viewStmt, _ = sq.Select(
|
||||
LoginNameUserCol,
|
||||
alias(
|
||||
whenThenElse(
|
||||
LoginNamePoliciesMustBeDomainCol,
|
||||
concat(LoginNameUserUserNameCol, "'@'", domainAlias),
|
||||
LoginNameUserUserNameCol),
|
||||
LoginNameCol),
|
||||
alias(coalesce(LoginNameDomainIsPrimaryCol, "true"),
|
||||
LoginNameIsPrimaryCol),
|
||||
LoginNameInstanceIDCol,
|
||||
).FromSelect(loginNamesTable, LoginNameTableAlias).MustSql()
|
||||
)
|
||||
|
||||
func col(table, name string) string {
|
||||
return table + "." + name
|
||||
}
|
||||
|
||||
func alias(col, alias string) string {
|
||||
return col + " AS " + alias
|
||||
}
|
||||
|
||||
func coalesce(values ...string) string {
|
||||
str := "COALESCE("
|
||||
for i, value := range values {
|
||||
if i > 0 {
|
||||
str += ", "
|
||||
}
|
||||
str += value
|
||||
}
|
||||
str += ")"
|
||||
return str
|
||||
}
|
||||
|
||||
func eq(first, second string) string {
|
||||
return first + " = " + second
|
||||
}
|
||||
|
||||
func leftJoin(table, alias, on string, and ...string) string {
|
||||
st := table + " " + alias + " ON " + on
|
||||
for _, a := range and {
|
||||
st += " AND " + a
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
||||
func concat(strs ...string) string {
|
||||
return "CONCAT(" + strings.Join(strs, ", ") + ")"
|
||||
}
|
||||
|
||||
func whenThenElse(when, then, el string) string {
|
||||
return "(CASE WHEN " + when + " THEN " + then + " ELSE " + el + " END)"
|
||||
}
|
||||
//go:embed login_name_query.sql
|
||||
var loginNameViewStmt string
|
||||
|
||||
type loginNameProjection struct{}
|
||||
|
||||
@@ -170,7 +71,7 @@ func (*loginNameProjection) Name() string {
|
||||
|
||||
func (*loginNameProjection) Init() *old_handler.Check {
|
||||
return handler.NewViewCheck(
|
||||
viewStmt,
|
||||
loginNameViewStmt,
|
||||
handler.NewSuffixedTable(
|
||||
[]*handler.InitColumn{
|
||||
handler.NewColumn(LoginNameUserIDCol, handler.ColumnTypeText),
|
||||
@@ -229,7 +130,9 @@ func (*loginNameProjection) Init() *old_handler.Check {
|
||||
},
|
||||
handler.NewPrimaryKey(LoginNamePoliciesInstanceIDCol, LoginNamePoliciesResourceOwnerCol),
|
||||
loginNamePolicySuffix,
|
||||
handler.WithIndex(handler.NewIndex("is_default", []string{LoginNamePoliciesResourceOwnerCol, LoginNamePoliciesIsDefaultCol})),
|
||||
// this index is not used anymore, but kept for understanding why the default exists on existing systems, TODO: remove in login_names4
|
||||
// handler.WithIndex(handler.NewIndex("is_default", []string{LoginNamePoliciesResourceOwnerCol, LoginNamePoliciesIsDefaultCol})),
|
||||
handler.WithIndex(handler.NewIndex("is_default_owner", []string{LoginNamePoliciesInstanceIDCol, LoginNamePoliciesIsDefaultCol, LoginNamePoliciesResourceOwnerCol}, handler.WithInclude(LoginNamePoliciesMustBeDomainCol))),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
35
internal/query/projection/login_name_query.sql
Normal file
35
internal/query/projection/login_name_query.sql
Normal file
@@ -0,0 +1,35 @@
|
||||
SELECT
|
||||
u.id AS user_id
|
||||
, CASE
|
||||
WHEN p.must_be_domain THEN CONCAT(u.user_name, '@', d.name)
|
||||
ELSE u.user_name
|
||||
END AS login_name
|
||||
, COALESCE(d.is_primary, TRUE) AS is_primary
|
||||
, u.instance_id
|
||||
FROM
|
||||
projections.login_names3_users AS u
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT
|
||||
must_be_domain
|
||||
, is_default
|
||||
FROM
|
||||
projections.login_names3_policies AS p
|
||||
WHERE
|
||||
(
|
||||
p.instance_id = u.instance_id
|
||||
AND NOT p.is_default
|
||||
AND p.resource_owner = u.resource_owner
|
||||
) OR (
|
||||
p.instance_id = u.instance_id
|
||||
AND p.is_default
|
||||
)
|
||||
ORDER BY
|
||||
p.is_default -- custom first
|
||||
LIMIT 1
|
||||
) AS p ON TRUE
|
||||
LEFT JOIN
|
||||
projections.login_names3_domains d
|
||||
ON
|
||||
p.must_be_domain
|
||||
AND u.resource_owner = d.resource_owner
|
||||
AND u.instance_id = d.instance_id
|
@@ -124,6 +124,7 @@ func (*userProjection) Init() *old_handler.Check {
|
||||
handler.NewPrimaryKey(HumanUserInstanceIDCol, HumanUserIDCol),
|
||||
UserHumanSuffix,
|
||||
handler.WithForeignKey(handler.NewForeignKeyOfPublicKeys()),
|
||||
handler.WithIndex(handler.NewIndex("email", []string{HumanUserInstanceIDCol, "LOWER(" + HumanEmailCol + ")"})),
|
||||
),
|
||||
handler.NewSuffixedTable([]*handler.InitColumn{
|
||||
handler.NewColumn(MachineUserIDCol, handler.ColumnTypeText),
|
||||
|
@@ -78,7 +78,7 @@ func (q *Queries) GetInstanceRestrictions(ctx context.Context) (restrictions Res
|
||||
RestrictionsColumnResourceOwner.identifier(): instanceID,
|
||||
}).ToSql()
|
||||
if err != nil {
|
||||
return restrictions, zitade_errors.ThrowInternal(err, "QUERY-XnLMQ", "Errors.Query.SQLStatment")
|
||||
return restrictions, zitade_errors.ThrowInternal(err, "QUERY-XnLMQ", "Errors.Query.SQLStatement")
|
||||
}
|
||||
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
||||
restrictions, err = scan(row)
|
||||
|
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
@@ -334,23 +335,23 @@ func (q *textQuery) comp() sq.Sqlizer {
|
||||
case TextNotEquals:
|
||||
return sq.NotEq{q.Column.identifier(): q.Text}
|
||||
case TextEqualsIgnoreCase:
|
||||
return sq.ILike{q.Column.identifier(): q.Text}
|
||||
return sq.Like{"LOWER(" + q.Column.identifier() + ")": strings.ToLower(q.Text)}
|
||||
case TextNotEqualsIgnoreCase:
|
||||
return sq.NotILike{q.Column.identifier(): q.Text}
|
||||
return sq.NotLike{"LOWER(" + q.Column.identifier() + ")": strings.ToLower(q.Text)}
|
||||
case TextStartsWith:
|
||||
return sq.Like{q.Column.identifier(): q.Text + "%"}
|
||||
case TextStartsWithIgnoreCase:
|
||||
return sq.ILike{q.Column.identifier(): q.Text + "%"}
|
||||
return sq.Like{"LOWER(" + q.Column.identifier() + ")": strings.ToLower(q.Text) + "%"}
|
||||
case TextEndsWith:
|
||||
return sq.Like{q.Column.identifier(): "%" + q.Text}
|
||||
case TextEndsWithIgnoreCase:
|
||||
return sq.ILike{q.Column.identifier(): "%" + q.Text}
|
||||
return sq.Like{"LOWER(" + q.Column.identifier() + ")": "%" + strings.ToLower(q.Text)}
|
||||
case TextContains:
|
||||
return sq.Like{q.Column.identifier(): "%" + q.Text + "%"}
|
||||
case TextContainsIgnoreCase:
|
||||
return sq.ILike{q.Column.identifier(): "%" + q.Text + "%"}
|
||||
return sq.Like{"LOWER(" + q.Column.identifier() + ")": "%" + strings.ToLower(q.Text) + "%"}
|
||||
case TextListContains:
|
||||
return &listContains{col: q.Column, args: []interface{}{q.Text}}
|
||||
return &listContains{col: q.Column, args: []any{q.Text}}
|
||||
case textCompareMax:
|
||||
return nil
|
||||
}
|
||||
|
@@ -1204,7 +1204,7 @@ func TestTextQuery_comp(t *testing.T) {
|
||||
Compare: TextEqualsIgnoreCase,
|
||||
},
|
||||
want: want{
|
||||
query: sq.ILike{"test_table.test_col": "Hurst"},
|
||||
query: sq.Like{"LOWER(test_table.test_col)": "hurst"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -1226,7 +1226,7 @@ func TestTextQuery_comp(t *testing.T) {
|
||||
Compare: TextNotEqualsIgnoreCase,
|
||||
},
|
||||
want: want{
|
||||
query: sq.NotILike{"test_table.test_col": "Hurst"},
|
||||
query: sq.NotLike{"LOWER(test_table.test_col)": "hurst"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -1237,7 +1237,7 @@ func TestTextQuery_comp(t *testing.T) {
|
||||
Compare: TextEqualsIgnoreCase,
|
||||
},
|
||||
want: want{
|
||||
query: sq.ILike{"test_table.test_col": "Hu\\%\\%rst"},
|
||||
query: sq.Like{"LOWER(test_table.test_col)": "hu\\%\\%rst"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -1270,7 +1270,7 @@ func TestTextQuery_comp(t *testing.T) {
|
||||
Compare: TextStartsWithIgnoreCase,
|
||||
},
|
||||
want: want{
|
||||
query: sq.ILike{"test_table.test_col": "Hurst%"},
|
||||
query: sq.Like{"LOWER(test_table.test_col)": "hurst%"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -1281,7 +1281,7 @@ func TestTextQuery_comp(t *testing.T) {
|
||||
Compare: TextStartsWithIgnoreCase,
|
||||
},
|
||||
want: want{
|
||||
query: sq.ILike{"test_table.test_col": "Hurst\\%%"},
|
||||
query: sq.Like{"LOWER(test_table.test_col)": "hurst\\%%"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -1314,7 +1314,7 @@ func TestTextQuery_comp(t *testing.T) {
|
||||
Compare: TextEndsWithIgnoreCase,
|
||||
},
|
||||
want: want{
|
||||
query: sq.ILike{"test_table.test_col": "%Hurst"},
|
||||
query: sq.Like{"LOWER(test_table.test_col)": "%hurst"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -1325,7 +1325,7 @@ func TestTextQuery_comp(t *testing.T) {
|
||||
Compare: TextEndsWithIgnoreCase,
|
||||
},
|
||||
want: want{
|
||||
query: sq.ILike{"test_table.test_col": "%\\%Hurst"},
|
||||
query: sq.Like{"LOWER(test_table.test_col)": "%\\%hurst"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -1351,14 +1351,14 @@ func TestTextQuery_comp(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "containts ignore case",
|
||||
name: "contains ignore case",
|
||||
fields: fields{
|
||||
Column: testCol,
|
||||
Text: "Hurst",
|
||||
Compare: TextContainsIgnoreCase,
|
||||
},
|
||||
want: want{
|
||||
query: sq.ILike{"test_table.test_col": "%Hurst%"},
|
||||
query: sq.Like{"LOWER(test_table.test_col)": "%hurst%"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -1369,11 +1369,11 @@ func TestTextQuery_comp(t *testing.T) {
|
||||
Compare: TextContainsIgnoreCase,
|
||||
},
|
||||
want: want{
|
||||
query: sq.ILike{"test_table.test_col": "%\\%Hurst\\%%"},
|
||||
query: sq.Like{"LOWER(test_table.test_col)": "%\\%hurst\\%%"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list containts",
|
||||
name: "list contains",
|
||||
fields: fields{
|
||||
Column: testCol,
|
||||
Text: "Hurst",
|
||||
|
@@ -132,7 +132,7 @@ func (q *Queries) SecretGeneratorByType(ctx context.Context, generatorType domai
|
||||
SecretGeneratorColumnInstanceID.identifier(): instanceID,
|
||||
}).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-3k99f", "Errors.Query.SQLStatment")
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-3k99f", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
||||
|
@@ -67,7 +67,7 @@ func (q *Queries) SecurityPolicy(ctx context.Context) (policy *SecurityPolicy, e
|
||||
SecurityPolicyColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
}).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Sf6d1", "Errors.Query.SQLStatment")
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Sf6d1", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
||||
|
@@ -200,21 +200,15 @@ var (
|
||||
|
||||
userLoginNamesTable = loginNameTable.setAlias("login_names")
|
||||
userLoginNamesUserIDCol = LoginNameUserIDCol.setTable(userLoginNamesTable)
|
||||
userLoginNamesNameCol = LoginNameNameCol.setTable(userLoginNamesTable)
|
||||
userLoginNamesInstanceIDCol = LoginNameInstanceIDCol.setTable(userLoginNamesTable)
|
||||
userLoginNamesListCol = Column{
|
||||
name: "loginnames",
|
||||
name: "login_names",
|
||||
table: userLoginNamesTable,
|
||||
}
|
||||
userLoginNamesLowerListCol = Column{
|
||||
name: "loginnames_lower",
|
||||
userPreferredLoginNameCol = Column{
|
||||
name: "preferred_login_name",
|
||||
table: userLoginNamesTable,
|
||||
}
|
||||
userPreferredLoginNameTable = loginNameTable.setAlias("preferred_login_name")
|
||||
userPreferredLoginNameUserIDCol = LoginNameUserIDCol.setTable(userPreferredLoginNameTable)
|
||||
userPreferredLoginNameCol = LoginNameNameCol.setTable(userPreferredLoginNameTable)
|
||||
userPreferredLoginNameIsPrimaryCol = LoginNameIsPrimaryCol.setTable(userPreferredLoginNameTable)
|
||||
userPreferredLoginNameInstanceIDCol = LoginNameInstanceIDCol.setTable(userPreferredLoginNameTable)
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -459,7 +453,7 @@ func (q *Queries) GetHumanProfile(ctx context.Context, userID string, queries ..
|
||||
}
|
||||
stmt, args, err := query.Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Dgbg2", "Errors.Query.SQLStatment")
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Dgbg2", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
||||
@@ -483,7 +477,7 @@ func (q *Queries) GetHumanEmail(ctx context.Context, userID string, queries ...S
|
||||
}
|
||||
stmt, args, err := query.Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-BHhj3", "Errors.Query.SQLStatment")
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-BHhj3", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
||||
@@ -507,7 +501,7 @@ func (q *Queries) GetHumanPhone(ctx context.Context, userID string, queries ...S
|
||||
}
|
||||
stmt, args, err := query.Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Dg43g", "Errors.Query.SQLStatment")
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Dg43g", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
||||
@@ -593,7 +587,7 @@ func (q *Queries) GetNotifyUser(ctx context.Context, shouldTriggered bool, queri
|
||||
}
|
||||
stmt, args, err := query.Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Err3g", "Errors.Query.SQLStatment")
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Err3g", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
||||
@@ -611,7 +605,7 @@ func (q *Queries) CountUsers(ctx context.Context, queries *UserSearchQueries) (c
|
||||
eq := sq.Eq{UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID()}
|
||||
stmt, args, err := queries.toQuery(query).Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return 0, zerrors.ThrowInternal(err, "QUERY-w3Dx", "Errors.Query.SQLStatment")
|
||||
return 0, zerrors.ThrowInternal(err, "QUERY-w3Dx", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
err = q.client.QueryContext(ctx, func(rows *sql.Rows) error {
|
||||
@@ -646,7 +640,7 @@ func (q *Queries) searchUsers(ctx context.Context, queries *UserSearchQueries, p
|
||||
UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
}).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Dgbg2", "Errors.Query.SQLStatment")
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Dgbg2", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
err = q.client.QueryContext(ctx, func(rows *sql.Rows) error {
|
||||
@@ -693,7 +687,7 @@ func (q *Queries) IsUserUnique(ctx context.Context, username, email, resourceOwn
|
||||
eq := sq.Eq{UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID()}
|
||||
stmt, args, err := query.Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return false, zerrors.ThrowInternal(err, "QUERY-Dg43g", "Errors.Query.SQLStatment")
|
||||
return false, zerrors.ThrowInternal(err, "QUERY-Dg43g", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
||||
@@ -792,12 +786,8 @@ func NewUserPreferredLoginNameSearchQuery(value string, comparison TextCompariso
|
||||
return NewTextQuery(userPreferredLoginNameCol, value, comparison)
|
||||
}
|
||||
|
||||
func NewUserLoginNamesSearchQuery(value string) (SearchQuery, error) {
|
||||
return NewTextQuery(userLoginNamesLowerListCol, strings.ToLower(value), TextListContains)
|
||||
}
|
||||
|
||||
func NewUserLoginNameExistsQuery(value string, comparison TextComparison) (SearchQuery, error) {
|
||||
// linking queries for the subselect
|
||||
// linking queries for the sub select
|
||||
instanceQuery, err := NewColumnComparisonQuery(LoginNameInstanceIDCol, UserInstanceIDCol, ColumnEquals)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -828,30 +818,16 @@ func triggerUserProjections(ctx context.Context) {
|
||||
triggerBatch(ctx, projection.UserProjection, projection.LoginNameProjection)
|
||||
}
|
||||
|
||||
func prepareLoginNamesQuery() (string, []interface{}, error) {
|
||||
return sq.Select(
|
||||
userLoginNamesUserIDCol.identifier(),
|
||||
"ARRAY_AGG("+userLoginNamesNameCol.identifier()+")::TEXT[] AS "+userLoginNamesListCol.name,
|
||||
"ARRAY_AGG(LOWER("+userLoginNamesNameCol.identifier()+"))::TEXT[] AS "+userLoginNamesLowerListCol.name,
|
||||
userLoginNamesInstanceIDCol.identifier(),
|
||||
).From(userLoginNamesTable.identifier()).
|
||||
GroupBy(
|
||||
userLoginNamesUserIDCol.identifier(),
|
||||
userLoginNamesInstanceIDCol.identifier(),
|
||||
).ToSql()
|
||||
}
|
||||
|
||||
func preparePreferredLoginNamesQuery() (string, []interface{}, error) {
|
||||
return sq.Select(
|
||||
userPreferredLoginNameUserIDCol.identifier(),
|
||||
userPreferredLoginNameCol.identifier(),
|
||||
userPreferredLoginNameInstanceIDCol.identifier(),
|
||||
).From(userPreferredLoginNameTable.identifier()).
|
||||
Where(sq.Eq{
|
||||
userPreferredLoginNameIsPrimaryCol.identifier(): true,
|
||||
},
|
||||
).ToSql()
|
||||
}
|
||||
var joinLoginNames = `LEFT JOIN LATERAL (` +
|
||||
`SELECT` +
|
||||
` ARRAY_AGG(ln.login_name ORDER BY ln.login_name) AS login_names,` +
|
||||
` MAX(CASE WHEN ln.is_primary THEN ln.login_name ELSE NULL END) AS preferred_login_name` +
|
||||
` FROM` +
|
||||
` projections.login_names3 AS ln` +
|
||||
` WHERE` +
|
||||
` ln.user_id = ` + UserIDCol.identifier() +
|
||||
` AND ln.instance_id = ` + UserInstanceIDCol.identifier() +
|
||||
`) AS login_names ON TRUE`
|
||||
|
||||
func scanUser(row *sql.Row) (*User, error) {
|
||||
u := new(User)
|
||||
@@ -951,64 +927,6 @@ func scanUser(row *sql.Row) (*User, error) {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func prepareUserQuery() (sq.SelectBuilder, func(*sql.Row) (*User, error)) {
|
||||
loginNamesQuery, loginNamesArgs, err := prepareLoginNamesQuery()
|
||||
if err != nil {
|
||||
return sq.SelectBuilder{}, nil
|
||||
}
|
||||
preferredLoginNameQuery, preferredLoginNameArgs, err := preparePreferredLoginNamesQuery()
|
||||
if err != nil {
|
||||
return sq.SelectBuilder{}, nil
|
||||
}
|
||||
return sq.Select(
|
||||
UserIDCol.identifier(),
|
||||
UserCreationDateCol.identifier(),
|
||||
UserChangeDateCol.identifier(),
|
||||
UserResourceOwnerCol.identifier(),
|
||||
UserSequenceCol.identifier(),
|
||||
UserStateCol.identifier(),
|
||||
UserTypeCol.identifier(),
|
||||
UserUsernameCol.identifier(),
|
||||
userLoginNamesListCol.identifier(),
|
||||
userPreferredLoginNameCol.identifier(),
|
||||
HumanUserIDCol.identifier(),
|
||||
HumanFirstNameCol.identifier(),
|
||||
HumanLastNameCol.identifier(),
|
||||
HumanNickNameCol.identifier(),
|
||||
HumanDisplayNameCol.identifier(),
|
||||
HumanPreferredLanguageCol.identifier(),
|
||||
HumanGenderCol.identifier(),
|
||||
HumanAvatarURLCol.identifier(),
|
||||
HumanEmailCol.identifier(),
|
||||
HumanIsEmailVerifiedCol.identifier(),
|
||||
HumanPhoneCol.identifier(),
|
||||
HumanIsPhoneVerifiedCol.identifier(),
|
||||
HumanPasswordChangeRequiredCol.identifier(),
|
||||
HumanPasswordChangedCol.identifier(),
|
||||
HumanMFAInitSkippedCol.identifier(),
|
||||
MachineUserIDCol.identifier(),
|
||||
MachineNameCol.identifier(),
|
||||
MachineDescriptionCol.identifier(),
|
||||
MachineSecretCol.identifier(),
|
||||
MachineAccessTokenTypeCol.identifier(),
|
||||
countColumn.identifier(),
|
||||
).
|
||||
From(userTable.identifier()).
|
||||
LeftJoin(join(HumanUserIDCol, UserIDCol)).
|
||||
LeftJoin(join(MachineUserIDCol, UserIDCol)).
|
||||
LeftJoin("("+loginNamesQuery+") AS "+userLoginNamesTable.alias+" ON "+
|
||||
userLoginNamesUserIDCol.identifier()+" = "+UserIDCol.identifier()+" AND "+
|
||||
userLoginNamesInstanceIDCol.identifier()+" = "+UserInstanceIDCol.identifier(),
|
||||
loginNamesArgs...).
|
||||
LeftJoin("("+preferredLoginNameQuery+") AS "+userPreferredLoginNameTable.alias+" ON "+
|
||||
userPreferredLoginNameUserIDCol.identifier()+" = "+UserIDCol.identifier()+" AND "+
|
||||
userPreferredLoginNameInstanceIDCol.identifier()+" = "+UserInstanceIDCol.identifier(),
|
||||
preferredLoginNameArgs...).
|
||||
PlaceholderFormat(sq.Dollar),
|
||||
|
||||
scanUser
|
||||
}
|
||||
|
||||
func prepareProfileQuery() (sq.SelectBuilder, func(*sql.Row) (*Profile, error)) {
|
||||
return sq.Select(
|
||||
UserIDCol.identifier(),
|
||||
@@ -1170,14 +1088,6 @@ func preparePhoneQuery() (sq.SelectBuilder, func(*sql.Row) (*Phone, error)) {
|
||||
}
|
||||
|
||||
func prepareNotifyUserQuery() (sq.SelectBuilder, func(*sql.Row) (*NotifyUser, error)) {
|
||||
loginNamesQuery, loginNamesArgs, err := prepareLoginNamesQuery()
|
||||
if err != nil {
|
||||
return sq.SelectBuilder{}, nil
|
||||
}
|
||||
preferredLoginNameQuery, preferredLoginNameArgs, err := preparePreferredLoginNamesQuery()
|
||||
if err != nil {
|
||||
return sq.SelectBuilder{}, nil
|
||||
}
|
||||
return sq.Select(
|
||||
UserIDCol.identifier(),
|
||||
UserCreationDateCol.identifier(),
|
||||
@@ -1208,14 +1118,7 @@ func prepareNotifyUserQuery() (sq.SelectBuilder, func(*sql.Row) (*NotifyUser, er
|
||||
From(userTable.identifier()).
|
||||
LeftJoin(join(HumanUserIDCol, UserIDCol)).
|
||||
LeftJoin(join(NotifyUserIDCol, UserIDCol)).
|
||||
LeftJoin("("+loginNamesQuery+") AS "+userLoginNamesTable.alias+" ON "+
|
||||
userLoginNamesUserIDCol.identifier()+" = "+UserIDCol.identifier()+" AND "+
|
||||
userLoginNamesInstanceIDCol.identifier()+" = "+UserInstanceIDCol.identifier(),
|
||||
loginNamesArgs...).
|
||||
LeftJoin("("+preferredLoginNameQuery+") AS "+userPreferredLoginNameTable.alias+" ON "+
|
||||
userPreferredLoginNameUserIDCol.identifier()+" = "+UserIDCol.identifier()+" AND "+
|
||||
userPreferredLoginNameInstanceIDCol.identifier()+" = "+UserInstanceIDCol.identifier(),
|
||||
preferredLoginNameArgs...).
|
||||
JoinClause(joinLoginNames).
|
||||
PlaceholderFormat(sq.Dollar),
|
||||
scanNotifyUser
|
||||
}
|
||||
@@ -1359,14 +1262,6 @@ func prepareUserUniqueQuery() (sq.SelectBuilder, func(*sql.Row) (bool, error)) {
|
||||
}
|
||||
|
||||
func prepareUsersQuery() (sq.SelectBuilder, func(*sql.Rows) (*Users, error)) {
|
||||
loginNamesQuery, loginNamesArgs, err := prepareLoginNamesQuery()
|
||||
if err != nil {
|
||||
return sq.SelectBuilder{}, nil
|
||||
}
|
||||
preferredLoginNameQuery, preferredLoginNameArgs, err := preparePreferredLoginNamesQuery()
|
||||
if err != nil {
|
||||
return sq.SelectBuilder{}, nil
|
||||
}
|
||||
return sq.Select(
|
||||
UserIDCol.identifier(),
|
||||
UserCreationDateCol.identifier(),
|
||||
@@ -1401,14 +1296,7 @@ func prepareUsersQuery() (sq.SelectBuilder, func(*sql.Rows) (*Users, error)) {
|
||||
From(userTable.identifier()).
|
||||
LeftJoin(join(HumanUserIDCol, UserIDCol)).
|
||||
LeftJoin(join(MachineUserIDCol, UserIDCol)).
|
||||
LeftJoin("("+loginNamesQuery+") AS "+userLoginNamesTable.alias+" ON "+
|
||||
userLoginNamesUserIDCol.identifier()+" = "+UserIDCol.identifier()+" AND "+
|
||||
userLoginNamesInstanceIDCol.identifier()+" = "+UserInstanceIDCol.identifier(),
|
||||
loginNamesArgs...).
|
||||
LeftJoin("("+preferredLoginNameQuery+") AS "+userPreferredLoginNameTable.alias+" ON "+
|
||||
userPreferredLoginNameUserIDCol.identifier()+" = "+UserIDCol.identifier()+" AND "+
|
||||
userPreferredLoginNameInstanceIDCol.identifier()+" = "+UserInstanceIDCol.identifier(),
|
||||
preferredLoginNameArgs...).
|
||||
JoinClause(joinLoginNames).
|
||||
PlaceholderFormat(sq.Dollar),
|
||||
func(rows *sql.Rows) (*Users, error) {
|
||||
users := make([]*User, 0)
|
||||
|
@@ -1,41 +1,3 @@
|
||||
WITH login_names AS (SELECT
|
||||
u.id user_id
|
||||
, u.instance_id
|
||||
, u.resource_owner
|
||||
, u.user_name
|
||||
, d.name domain_name
|
||||
, d.is_primary
|
||||
, p.must_be_domain
|
||||
, CASE WHEN p.must_be_domain
|
||||
THEN concat(u.user_name, '@', d.name)
|
||||
ELSE u.user_name
|
||||
END login_name
|
||||
FROM
|
||||
projections.login_names3_users u
|
||||
JOIN lateral (
|
||||
SELECT
|
||||
p.must_be_domain
|
||||
FROM
|
||||
projections.login_names3_policies p
|
||||
WHERE
|
||||
u.instance_id = p.instance_id
|
||||
AND (
|
||||
(p.is_default IS TRUE AND p.instance_id = $3)
|
||||
OR (p.instance_id = $3 AND p.resource_owner = u.resource_owner)
|
||||
)
|
||||
ORDER BY is_default
|
||||
LIMIT 1
|
||||
) p ON TRUE
|
||||
JOIN
|
||||
projections.login_names3_domains d
|
||||
ON
|
||||
u.instance_id = d.instance_id
|
||||
AND u.resource_owner = d.resource_owner
|
||||
WHERE
|
||||
u.id = $1
|
||||
AND (u.resource_owner = $2 OR $2 = '')
|
||||
AND u.instance_id = $3
|
||||
)
|
||||
SELECT
|
||||
u.id
|
||||
, u.creation_date
|
||||
@@ -45,8 +7,8 @@ SELECT
|
||||
, u.state
|
||||
, u.type
|
||||
, u.username
|
||||
, (SELECT array_agg(ln.login_name)::TEXT[] login_names FROM login_names ln GROUP BY ln.user_id, ln.instance_id) login_names
|
||||
, (SELECT ln.login_name login_names_lower FROM login_names ln WHERE ln.is_primary IS TRUE) preferred_login_name
|
||||
, login_names.login_names AS login_names
|
||||
, login_names.preferred_login_name AS preferred_login_name
|
||||
, h.user_id
|
||||
, h.first_name
|
||||
, h.last_name
|
||||
@@ -79,6 +41,16 @@ LEFT JOIN
|
||||
ON
|
||||
u.id = m.user_id
|
||||
AND u.instance_id = m.instance_id
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT
|
||||
ARRAY_AGG(ln.login_name ORDER BY ln.login_name) AS login_names,
|
||||
MAX(CASE WHEN ln.is_primary THEN ln.login_name ELSE NULL END) AS preferred_login_name
|
||||
FROM
|
||||
projections.login_names3 AS ln
|
||||
WHERE
|
||||
ln.user_id = u.id
|
||||
AND ln.instance_id = u.instance_id
|
||||
) AS login_names ON TRUE
|
||||
WHERE
|
||||
u.id = $1
|
||||
AND (u.resource_owner = $2 OR $2 = '')
|
||||
|
@@ -97,7 +97,7 @@ func (q *Queries) GetUserMetadataByKey(ctx context.Context, shouldTriggerBulk bo
|
||||
}
|
||||
stmt, args, err := query.Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-aDGG2", "Errors.Query.SQLStatment")
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-aDGG2", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
||||
@@ -125,7 +125,7 @@ func (q *Queries) SearchUserMetadataForUsers(ctx context.Context, shouldTriggerB
|
||||
}
|
||||
stmt, args, err := queries.toQuery(query).Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Egbgd", "Errors.Query.SQLStatment")
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Egbgd", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
err = q.client.QueryContext(ctx, func(rows *sql.Rows) error {
|
||||
@@ -157,7 +157,7 @@ func (q *Queries) SearchUserMetadata(ctx context.Context, shouldTriggerBulk bool
|
||||
}
|
||||
stmt, args, err := queries.toQuery(query).Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Egbgd", "Errors.Query.SQLStatment")
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Egbgd", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
err = q.client.QueryContext(ctx, func(rows *sql.Rows) error {
|
||||
|
@@ -1,41 +1,3 @@
|
||||
WITH login_names AS (
|
||||
SELECT
|
||||
u.id user_id
|
||||
, u.instance_id
|
||||
, u.resource_owner
|
||||
, u.user_name
|
||||
, d.name domain_name
|
||||
, d.is_primary
|
||||
, p.must_be_domain
|
||||
, CASE WHEN p.must_be_domain
|
||||
THEN concat(u.user_name, '@', d.name)
|
||||
ELSE u.user_name
|
||||
END login_name
|
||||
FROM
|
||||
projections.login_names3_users u
|
||||
JOIN lateral (
|
||||
SELECT
|
||||
p.must_be_domain
|
||||
FROM
|
||||
projections.login_names3_policies p
|
||||
WHERE
|
||||
u.instance_id = p.instance_id
|
||||
AND (
|
||||
(p.is_default IS TRUE AND p.instance_id = $2)
|
||||
OR (p.instance_id = $2 AND p.resource_owner = u.resource_owner)
|
||||
)
|
||||
ORDER BY is_default
|
||||
LIMIT 1
|
||||
) p ON TRUE
|
||||
JOIN
|
||||
projections.login_names3_domains d
|
||||
ON
|
||||
u.instance_id = d.instance_id
|
||||
AND u.resource_owner = d.resource_owner
|
||||
WHERE
|
||||
u.instance_id = $2
|
||||
AND u.id = $1
|
||||
)
|
||||
SELECT
|
||||
u.id
|
||||
, u.creation_date
|
||||
@@ -45,8 +7,8 @@ SELECT
|
||||
, u.state
|
||||
, u.type
|
||||
, u.username
|
||||
, (SELECT array_agg(ln.login_name)::TEXT[] login_names FROM login_names ln GROUP BY ln.user_id, ln.instance_id) login_names
|
||||
, (SELECT ln.login_name login_names_lower FROM login_names ln WHERE ln.is_primary IS TRUE) preferred_login_name
|
||||
, login_names.login_names AS login_names
|
||||
, login_names.preferred_login_name AS preferred_login_name
|
||||
, h.user_id
|
||||
, h.first_name
|
||||
, h.last_name
|
||||
@@ -73,6 +35,16 @@ LEFT JOIN
|
||||
ON
|
||||
u.id = n.user_id
|
||||
AND u.instance_id = n.instance_id
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT
|
||||
ARRAY_AGG(ln.login_name ORDER BY ln.login_name) AS login_names,
|
||||
MAX(CASE WHEN ln.is_primary THEN ln.login_name ELSE NULL END) AS preferred_login_name
|
||||
FROM
|
||||
projections.login_names3 AS ln
|
||||
WHERE
|
||||
ln.user_id = u.id
|
||||
AND ln.instance_id = u.instance_id
|
||||
) AS login_names ON TRUE
|
||||
WHERE
|
||||
u.id = $1
|
||||
AND u.instance_id = $2
|
||||
|
@@ -118,7 +118,7 @@ func (q *Queries) PersonalAccessTokenByID(ctx context.Context, shouldTriggerBulk
|
||||
}
|
||||
stmt, args, err := query.Where(eq).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Dgfb4", "Errors.Query.SQLStatment")
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Dgfb4", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
||||
|
@@ -222,87 +222,6 @@ func TestUser_userCheckPermission(t *testing.T) {
|
||||
}
|
||||
|
||||
var (
|
||||
loginNamesQuery = `SELECT login_names.user_id, ARRAY_AGG(login_names.login_name)::TEXT[] AS loginnames, ARRAY_AGG(LOWER(login_names.login_name))::TEXT[] AS loginnames_lower, login_names.instance_id` +
|
||||
` FROM projections.login_names3 AS login_names` +
|
||||
` GROUP BY login_names.user_id, login_names.instance_id`
|
||||
preferredLoginNameQuery = `SELECT preferred_login_name.user_id, preferred_login_name.login_name, preferred_login_name.instance_id` +
|
||||
` FROM projections.login_names3 AS preferred_login_name` +
|
||||
` WHERE preferred_login_name.is_primary = $1`
|
||||
userQuery = `SELECT projections.users14.id,` +
|
||||
` projections.users14.creation_date,` +
|
||||
` projections.users14.change_date,` +
|
||||
` projections.users14.resource_owner,` +
|
||||
` projections.users14.sequence,` +
|
||||
` projections.users14.state,` +
|
||||
` projections.users14.type,` +
|
||||
` projections.users14.username,` +
|
||||
` login_names.loginnames,` +
|
||||
` preferred_login_name.login_name,` +
|
||||
` projections.users14_humans.user_id,` +
|
||||
` projections.users14_humans.first_name,` +
|
||||
` projections.users14_humans.last_name,` +
|
||||
` projections.users14_humans.nick_name,` +
|
||||
` projections.users14_humans.display_name,` +
|
||||
` projections.users14_humans.preferred_language,` +
|
||||
` projections.users14_humans.gender,` +
|
||||
` projections.users14_humans.avatar_key,` +
|
||||
` projections.users14_humans.email,` +
|
||||
` projections.users14_humans.is_email_verified,` +
|
||||
` projections.users14_humans.phone,` +
|
||||
` projections.users14_humans.is_phone_verified,` +
|
||||
` projections.users14_humans.password_change_required,` +
|
||||
` projections.users14_humans.password_changed,` +
|
||||
` projections.users14_humans.mfa_init_skipped,` +
|
||||
` projections.users14_machines.user_id,` +
|
||||
` projections.users14_machines.name,` +
|
||||
` projections.users14_machines.description,` +
|
||||
` projections.users14_machines.secret,` +
|
||||
` projections.users14_machines.access_token_type,` +
|
||||
` COUNT(*) OVER ()` +
|
||||
` FROM projections.users14` +
|
||||
` LEFT JOIN projections.users14_humans ON projections.users14.id = projections.users14_humans.user_id AND projections.users14.instance_id = projections.users14_humans.instance_id` +
|
||||
` LEFT JOIN projections.users14_machines ON projections.users14.id = projections.users14_machines.user_id AND projections.users14.instance_id = projections.users14_machines.instance_id` +
|
||||
` LEFT JOIN` +
|
||||
` (` + loginNamesQuery + `) AS login_names` +
|
||||
` ON login_names.user_id = projections.users14.id AND login_names.instance_id = projections.users14.instance_id` +
|
||||
` LEFT JOIN` +
|
||||
` (` + preferredLoginNameQuery + `) AS preferred_login_name` +
|
||||
` ON preferred_login_name.user_id = projections.users14.id AND preferred_login_name.instance_id = projections.users14.instance_id`
|
||||
userCols = []string{
|
||||
"id",
|
||||
"creation_date",
|
||||
"change_date",
|
||||
"resource_owner",
|
||||
"sequence",
|
||||
"state",
|
||||
"type",
|
||||
"username",
|
||||
"loginnames",
|
||||
"login_name",
|
||||
// human
|
||||
"user_id",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"nick_name",
|
||||
"display_name",
|
||||
"preferred_language",
|
||||
"gender",
|
||||
"avatar_key",
|
||||
"email",
|
||||
"is_email_verified",
|
||||
"phone",
|
||||
"is_phone_verified",
|
||||
"password_change_required",
|
||||
"password_changed",
|
||||
"mfa_init_skipped",
|
||||
// machine
|
||||
"user_id",
|
||||
"name",
|
||||
"description",
|
||||
"secret",
|
||||
"access_token_type",
|
||||
"count",
|
||||
}
|
||||
profileQuery = `SELECT projections.users14.id,` +
|
||||
` projections.users14.creation_date,` +
|
||||
` projections.users14.change_date,` +
|
||||
@@ -397,8 +316,8 @@ var (
|
||||
` projections.users14.state,` +
|
||||
` projections.users14.type,` +
|
||||
` projections.users14.username,` +
|
||||
` login_names.loginnames,` +
|
||||
` preferred_login_name.login_name,` +
|
||||
` login_names.login_names,` +
|
||||
` login_names.preferred_login_name,` +
|
||||
` projections.users14_humans.user_id,` +
|
||||
` projections.users14_humans.first_name,` +
|
||||
` projections.users14_humans.last_name,` +
|
||||
@@ -417,12 +336,7 @@ var (
|
||||
` FROM projections.users14` +
|
||||
` LEFT JOIN projections.users14_humans ON projections.users14.id = projections.users14_humans.user_id AND projections.users14.instance_id = projections.users14_humans.instance_id` +
|
||||
` LEFT JOIN projections.users14_notifications ON projections.users14.id = projections.users14_notifications.user_id AND projections.users14.instance_id = projections.users14_notifications.instance_id` +
|
||||
` LEFT JOIN` +
|
||||
` (` + loginNamesQuery + `) AS login_names` +
|
||||
` ON login_names.user_id = projections.users14.id AND login_names.instance_id = projections.users14.instance_id` +
|
||||
` LEFT JOIN` +
|
||||
` (` + preferredLoginNameQuery + `) AS preferred_login_name` +
|
||||
` ON preferred_login_name.user_id = projections.users14.id AND preferred_login_name.instance_id = projections.users14.instance_id`
|
||||
` LEFT JOIN LATERAL (SELECT ARRAY_AGG(ln.login_name ORDER BY ln.login_name) AS login_names, MAX(CASE WHEN ln.is_primary THEN ln.login_name ELSE NULL END) AS preferred_login_name FROM projections.login_names3 AS ln WHERE ln.user_id = projections.users14.id AND ln.instance_id = projections.users14.instance_id) AS login_names ON TRUE`
|
||||
notifyUserCols = []string{
|
||||
"id",
|
||||
"creation_date",
|
||||
@@ -432,8 +346,8 @@ var (
|
||||
"state",
|
||||
"type",
|
||||
"username",
|
||||
"loginnames",
|
||||
"login_name",
|
||||
"login_names",
|
||||
"preferred_login_name",
|
||||
// human
|
||||
"user_id",
|
||||
"first_name",
|
||||
@@ -460,8 +374,8 @@ var (
|
||||
` projections.users14.state,` +
|
||||
` projections.users14.type,` +
|
||||
` projections.users14.username,` +
|
||||
` login_names.loginnames,` +
|
||||
` preferred_login_name.login_name,` +
|
||||
` login_names.login_names,` +
|
||||
` login_names.preferred_login_name,` +
|
||||
` projections.users14_humans.user_id,` +
|
||||
` projections.users14_humans.first_name,` +
|
||||
` projections.users14_humans.last_name,` +
|
||||
@@ -485,12 +399,7 @@ var (
|
||||
` FROM projections.users14` +
|
||||
` LEFT JOIN projections.users14_humans ON projections.users14.id = projections.users14_humans.user_id AND projections.users14.instance_id = projections.users14_humans.instance_id` +
|
||||
` LEFT JOIN projections.users14_machines ON projections.users14.id = projections.users14_machines.user_id AND projections.users14.instance_id = projections.users14_machines.instance_id` +
|
||||
` LEFT JOIN` +
|
||||
` (` + loginNamesQuery + `) AS login_names` +
|
||||
` ON login_names.user_id = projections.users14.id AND login_names.instance_id = projections.users14.instance_id` +
|
||||
` LEFT JOIN` +
|
||||
` (` + preferredLoginNameQuery + `) AS preferred_login_name` +
|
||||
` ON preferred_login_name.user_id = projections.users14.id AND preferred_login_name.instance_id = projections.users14.instance_id`
|
||||
` LEFT JOIN LATERAL (SELECT ARRAY_AGG(ln.login_name ORDER BY ln.login_name) AS login_names, MAX(CASE WHEN ln.is_primary THEN ln.login_name ELSE NULL END) AS preferred_login_name FROM projections.login_names3 AS ln WHERE ln.user_id = projections.users14.id AND ln.instance_id = projections.users14.instance_id) AS login_names ON TRUE`
|
||||
usersCols = []string{
|
||||
"id",
|
||||
"creation_date",
|
||||
@@ -500,8 +409,8 @@ var (
|
||||
"state",
|
||||
"type",
|
||||
"username",
|
||||
"loginnames",
|
||||
"login_name",
|
||||
"login_names",
|
||||
"preferred_login_name",
|
||||
// human
|
||||
"user_id",
|
||||
"first_name",
|
||||
@@ -540,240 +449,6 @@ func Test_UserPrepares(t *testing.T) {
|
||||
want want
|
||||
object interface{}
|
||||
}{
|
||||
{
|
||||
name: "prepareUserQuery no result",
|
||||
prepare: prepareUserQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueryScanErr(
|
||||
regexp.QuoteMeta(userQuery),
|
||||
nil,
|
||||
nil,
|
||||
),
|
||||
err: func(err error) (error, bool) {
|
||||
if !zerrors.IsNotFound(err) {
|
||||
return fmt.Errorf("err should be zitadel.NotFoundError got: %w", err), false
|
||||
}
|
||||
return nil, true
|
||||
},
|
||||
},
|
||||
object: (*User)(nil),
|
||||
},
|
||||
{
|
||||
name: "prepareUserQuery human found",
|
||||
prepare: prepareUserQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQuery(
|
||||
regexp.QuoteMeta(userQuery),
|
||||
userCols,
|
||||
[]driver.Value{
|
||||
"id",
|
||||
testNow,
|
||||
testNow,
|
||||
"resource_owner",
|
||||
uint64(20211108),
|
||||
domain.UserStateActive,
|
||||
domain.UserTypeHuman,
|
||||
"username",
|
||||
database.TextArray[string]{"login_name1", "login_name2"},
|
||||
"login_name1",
|
||||
// human
|
||||
"id",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"nick_name",
|
||||
"display_name",
|
||||
"de",
|
||||
domain.GenderUnspecified,
|
||||
"avatar_key",
|
||||
"email",
|
||||
true,
|
||||
"phone",
|
||||
true,
|
||||
true,
|
||||
testNow,
|
||||
testNow,
|
||||
// machine
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
1,
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &User{
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
ResourceOwner: "resource_owner",
|
||||
Sequence: 20211108,
|
||||
State: domain.UserStateActive,
|
||||
Type: domain.UserTypeHuman,
|
||||
Username: "username",
|
||||
LoginNames: database.TextArray[string]{"login_name1", "login_name2"},
|
||||
PreferredLoginName: "login_name1",
|
||||
Human: &Human{
|
||||
FirstName: "first_name",
|
||||
LastName: "last_name",
|
||||
NickName: "nick_name",
|
||||
DisplayName: "display_name",
|
||||
AvatarKey: "avatar_key",
|
||||
PreferredLanguage: language.German,
|
||||
Gender: domain.GenderUnspecified,
|
||||
Email: "email",
|
||||
IsEmailVerified: true,
|
||||
Phone: "phone",
|
||||
IsPhoneVerified: true,
|
||||
PasswordChangeRequired: true,
|
||||
PasswordChanged: testNow,
|
||||
MFAInitSkipped: testNow,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareUserQuery machine found",
|
||||
prepare: prepareUserQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQuery(
|
||||
regexp.QuoteMeta(userQuery),
|
||||
userCols,
|
||||
[]driver.Value{
|
||||
"id",
|
||||
testNow,
|
||||
testNow,
|
||||
"resource_owner",
|
||||
uint64(20211108),
|
||||
domain.UserStateActive,
|
||||
domain.UserTypeMachine,
|
||||
"username",
|
||||
database.TextArray[string]{"login_name1", "login_name2"},
|
||||
"login_name1",
|
||||
// human
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// machine
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
nil,
|
||||
domain.OIDCTokenTypeBearer,
|
||||
1,
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &User{
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
ResourceOwner: "resource_owner",
|
||||
Sequence: 20211108,
|
||||
State: domain.UserStateActive,
|
||||
Type: domain.UserTypeMachine,
|
||||
Username: "username",
|
||||
LoginNames: database.TextArray[string]{"login_name1", "login_name2"},
|
||||
PreferredLoginName: "login_name1",
|
||||
Machine: &Machine{
|
||||
Name: "name",
|
||||
Description: "description",
|
||||
EncodedSecret: "",
|
||||
AccessTokenType: domain.OIDCTokenTypeBearer,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareUserQuery machine with secret found",
|
||||
prepare: prepareUserQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQuery(
|
||||
regexp.QuoteMeta(userQuery),
|
||||
userCols,
|
||||
[]driver.Value{
|
||||
"id",
|
||||
testNow,
|
||||
testNow,
|
||||
"resource_owner",
|
||||
uint64(20211108),
|
||||
domain.UserStateActive,
|
||||
domain.UserTypeMachine,
|
||||
"username",
|
||||
database.TextArray[string]{"login_name1", "login_name2"},
|
||||
"login_name1",
|
||||
// human
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// machine
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
"secret",
|
||||
domain.OIDCTokenTypeBearer,
|
||||
1,
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &User{
|
||||
ID: "id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
ResourceOwner: "resource_owner",
|
||||
Sequence: 20211108,
|
||||
State: domain.UserStateActive,
|
||||
Type: domain.UserTypeMachine,
|
||||
Username: "username",
|
||||
LoginNames: database.TextArray[string]{"login_name1", "login_name2"},
|
||||
PreferredLoginName: "login_name1",
|
||||
Machine: &Machine{
|
||||
Name: "name",
|
||||
Description: "description",
|
||||
EncodedSecret: "secret",
|
||||
AccessTokenType: domain.OIDCTokenTypeBearer,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareUserQuery sql err",
|
||||
prepare: prepareUserQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueryErr(
|
||||
regexp.QuoteMeta(userQuery),
|
||||
sql.ErrConnDone,
|
||||
),
|
||||
err: func(err error) (error, bool) {
|
||||
if !errors.Is(err, sql.ErrConnDone) {
|
||||
return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false
|
||||
}
|
||||
return nil, true
|
||||
},
|
||||
},
|
||||
object: (*User)(nil),
|
||||
},
|
||||
{
|
||||
name: "prepareProfileQuery no result",
|
||||
prepare: prepareProfileQuery,
|
||||
|
@@ -13,7 +13,7 @@ export function createOrg(accessToken: string): Promise<Org> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let response = http.asyncRequest(
|
||||
'POST',
|
||||
url('/v2beta/organizations'),
|
||||
url('/v2/organizations'),
|
||||
JSON.stringify({
|
||||
name: `load-test-${new Date(Date.now()).toISOString()}`,
|
||||
}),
|
||||
|
@@ -15,7 +15,7 @@ export async function setup() {
|
||||
}
|
||||
|
||||
export default async function (data: any) {
|
||||
const human = await createHuman(`vu-${__VU}`, data.org, data.tokens.accessToken);
|
||||
const human = await createHuman(`vu-${__VU}-${new Date(Date.now()).getTime()}`, data.org, data.tokens.accessToken);
|
||||
const updateRes = await updateHuman(
|
||||
{
|
||||
profile: {
|
||||
|
@@ -30,7 +30,7 @@ export function createHuman(username: string, org: Org, accessToken: string): Pr
|
||||
familyName: 'Zitizen',
|
||||
},
|
||||
email: {
|
||||
email: `zitizen-@caos.ch`,
|
||||
email: `${username}@zitadel.com`,
|
||||
isVerified: true,
|
||||
},
|
||||
password: {
|
||||
@@ -50,11 +50,11 @@ export function createHuman(username: string, org: Org, accessToken: string): Pr
|
||||
response
|
||||
.then((res) => {
|
||||
check(res, {
|
||||
'create user is status ok': (r) => r.status === 201,
|
||||
'create user is status ok': (r) => r.status === 200,
|
||||
}) || reject(`unable to create user(username: ${username}) status: ${res.status} body: ${res.body}`);
|
||||
createHumanTrend.add(res.timings.duration);
|
||||
|
||||
const user = http.get(url(`/v2beta/users/${res.json('userId')!}`), {
|
||||
const user = http.get(url(`/v2/users/${res.json('userId')!}`), {
|
||||
headers: {
|
||||
authorization: `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
|
Reference in New Issue
Block a user