From 46f0107b1416bd0f6d02da5b46c59a3661405a0e Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Wed, 29 Jul 2026 23:38:32 +0800 Subject: [PATCH 01/22] fix(plan): validate child keys in modern update --- pkg/sql/plan/bind_update.go | 15 + pkg/sql/plan/bind_update_fk.go | 309 ++++++++++++++++++ pkg/sql/plan/build_test.go | 86 ++--- pkg/sql/plan/dml_context.go | 44 ++- pkg/sql/plan/update_planner_route_test.go | 303 ++++++++++++++++- .../cases/foreign_key/fk_self_refer.result | 2 +- .../cases/foreign_key/fk_self_refer2.result | 2 +- .../cases/foreign_key/fk_self_refer3.result | 4 +- .../cases/foreign_key/update_modern_fk.result | 96 ++++++ .../cases/foreign_key/update_modern_fk.sql | 83 +++++ 10 files changed, 871 insertions(+), 73 deletions(-) create mode 100644 pkg/sql/plan/bind_update_fk.go create mode 100644 test/distributed/cases/foreign_key/update_modern_fk.result create mode 100644 test/distributed/cases/foreign_key/update_modern_fk.sql diff --git a/pkg/sql/plan/bind_update.go b/pkg/sql/plan/bind_update.go index 962e606ff5834..5da98b6c309e4 100644 --- a/pkg/sql/plan/bind_update.go +++ b/pkg/sql/plan/bind_update.go @@ -354,6 +354,9 @@ func (builder *QueryBuilder) bindUpdate(stmt *tree.Update, bindCtx *BindContext) if col.OnUpdate != nil && col.OnUpdate.Expr != nil { newDefExpr := DeepCopyExpr(col.OnUpdate.Expr) err = replaceFuncId(builder.GetContext(), newDefExpr) + if err != nil { + return 0, err + } oldPos := oldColName2Idx[alias+"."+col.Name] newColName2Idx[alias+"."+col.Name] = oldPos @@ -418,6 +421,18 @@ func (builder *QueryBuilder) bindUpdate(stmt *tree.Update, bindCtx *BindContext) } } + lastNodeID, selectNodeTag, selectNode, err = builder.appendUpdateForeignKeyChecks( + bindCtx, + dmlCtx, + lastNodeID, + selectNodeTag, + oldColName2Idx, + newColName2Idx, + ) + if err != nil { + return 0, err + } + for i, tableDef := range dmlCtx.tableDefs { if updateAutoIncrCols[i] { lastNodeID = builder.appendNode(&plan.Node{ diff --git a/pkg/sql/plan/bind_update_fk.go b/pkg/sql/plan/bind_update_fk.go new file mode 100644 index 0000000000000..4770654c1ba41 --- /dev/null +++ b/pkg/sql/plan/bind_update_fk.go @@ -0,0 +1,309 @@ +// Copyright 2026 Matrix Origin +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package plan + +import ( + "github.com/matrixorigin/matrixone/pkg/common/moerr" + "github.com/matrixorigin/matrixone/pkg/pb/plan" +) + +func (builder *QueryBuilder) appendUpdateForeignKeyChecks( + bindCtx *BindContext, + dmlCtx *DMLContext, + lastNodeID int32, + selectNodeTag int32, + oldColName2Idx map[string]int32, + newColName2Idx map[string]int32, +) (int32, int32, *plan.Node, error) { + enabled, err := IsForeignKeyChecksEnabled(builder.compCtx) + if err != nil { + return 0, 0, nil, err + } + if !enabled { + return lastNodeID, selectNodeTag, builder.qry.Nodes[lastNodeID], nil + } + + for i, tableDef := range dmlCtx.tableDefs { + if len(dmlCtx.updateCol2Expr[i]) == 0 { + continue + } + + alias := dmlCtx.aliases[i] + if err = builder.rejectUpdateOfReferencedParentKey( + bindCtx, + tableDef, + alias, + newColName2Idx, + ); err != nil { + return 0, 0, nil, err + } + + affectedFks := affectedUpdateChildFks(tableDef, alias, newColName2Idx) + if len(affectedFks) == 0 { + continue + } + + fkTableDef := *tableDef + fkTableDef.Fkeys = make([]*plan.ForeignKeyDef, len(affectedFks)) + for j, fk := range affectedFks { + fkTableDef.Fkeys[j] = DeepCopyFkey(fk) + if fkTableDef.Fkeys[j].ForeignTbl == 0 { + fkTableDef.Fkeys[j].ForeignTbl = tableDef.TblId + } + } + + sourceNode := builder.qry.Nodes[lastNodeID] + projLen := len(sourceNode.ProjectList) + projectTypes := make([]plan.Type, projLen) + for j, expr := range sourceNode.ProjectList { + projectTypes[j] = expr.Typ + } + + var oks []*plan.Expr + lastNodeID, oks, err = builder.appendModernChildFkMarkOks( + bindCtx, + &fkTableDef, + lastNodeID, + selectNodeTag, + func(colName string) int32 { + qualifiedName := alias + "." + colName + if pos, ok := newColName2Idx[qualifiedName]; ok { + return pos + } + return oldColName2Idx[qualifiedName] + }, + ) + if err != nil { + return 0, 0, nil, err + } + + assertConds := make([]*plan.Expr, len(oks)) + errExpr := makePlan2StringConstExprWithType( + "Cannot add or update a child row: a foreign key constraint fails", + ) + for j, fk := range affectedFks { + unchanged, buildErr := builder.buildUpdateFkUnchangedExpr( + tableDef, + fk, + alias, + selectNodeTag, + oldColName2Idx, + newColName2Idx, + sourceNode, + ) + if buildErr != nil { + return 0, 0, nil, buildErr + } + ok := oks[j] + if unchanged != nil { + ok, err = BindFuncExprImplByPlanExpr( + builder.GetContext(), + "or", + []*plan.Expr{ok, unchanged}, + ) + if err != nil { + return 0, 0, nil, err + } + } + assertConds[j], err = BindFuncExprImplByPlanExpr( + builder.GetContext(), + "assert", + []*plan.Expr{ok, DeepCopyExpr(errExpr)}, + ) + if err != nil { + return 0, 0, nil, err + } + } + + lastNodeID = builder.appendNode(&plan.Node{ + NodeType: plan.Node_FILTER, + Children: []int32{lastNodeID}, + FilterList: assertConds, + }, bindCtx) + + validatedTag := builder.genNewBindTag() + projectList := make([]*plan.Expr, projLen) + for j := range projectList { + projectList[j] = &plan.Expr{ + Typ: projectTypes[j], + Expr: &plan.Expr_Col{ + Col: &plan.ColRef{ + RelPos: selectNodeTag, + ColPos: int32(j), + }, + }, + } + } + lastNodeID = builder.appendNode(&plan.Node{ + NodeType: plan.Node_PROJECT, + ProjectList: projectList, + Children: []int32{lastNodeID}, + BindingTags: []int32{validatedTag}, + }, bindCtx) + + selectNodeTag = validatedTag + } + + return lastNodeID, selectNodeTag, builder.qry.Nodes[lastNodeID], nil +} + +func affectedUpdateChildFks( + tableDef *plan.TableDef, + alias string, + newColName2Idx map[string]int32, +) []*plan.ForeignKeyDef { + if tableDef == nil { + return nil + } + + colIDToName := make(map[uint64]string, len(tableDef.Cols)) + for _, col := range tableDef.Cols { + colIDToName[col.ColId] = col.Name + } + + affected := make([]*plan.ForeignKeyDef, 0, len(tableDef.Fkeys)) + for _, fk := range tableDef.Fkeys { + for _, childColID := range fk.Cols { + if _, ok := newColName2Idx[alias+"."+colIDToName[childColID]]; ok { + affected = append(affected, fk) + break + } + } + } + return affected +} + +func (builder *QueryBuilder) rejectUpdateOfReferencedParentKey( + bindCtx *BindContext, + tableDef *plan.TableDef, + alias string, + newColName2Idx map[string]int32, +) error { + if tableDef == nil || len(tableDef.RefChildTbls) == 0 { + return nil + } + + parentColIDToName := make(map[uint64]string, len(tableDef.Cols)) + for _, col := range tableDef.Cols { + parentColIDToName[col.ColId] = col.Name + } + + visited := make(map[uint64]struct{}, len(tableDef.RefChildTbls)) + for _, childTableID := range tableDef.RefChildTbls { + if childTableID == 0 { + childTableID = tableDef.TblId + } + if _, ok := visited[childTableID]; ok { + continue + } + visited[childTableID] = struct{}{} + + _, childTableDef, err := builder.compCtx.ResolveById(childTableID, bindCtx.snapshot) + if err != nil { + return err + } + if childTableDef == nil { + return moerr.NewInternalErrorf( + builder.GetContext(), + "foreign-key child table %d not found", + childTableID, + ) + } + + for _, fk := range childTableDef.Fkeys { + referencesCurrentTable := fk.ForeignTbl == tableDef.TblId || + (fk.ForeignTbl == 0 && childTableDef.TblId == tableDef.TblId) + if !referencesCurrentTable { + continue + } + for _, parentColID := range fk.ForeignCols { + if _, ok := newColName2Idx[alias+"."+parentColIDToName[parentColID]]; ok { + return newLegacyUpdatePlannerRouteError( + updateRouteReasonForeignKey, + moerr.NewUnsupportedDML( + builder.GetContext(), + foreignKeyUnsupportedDMLCause, + ), + ) + } + } + } + } + return nil +} + +func (builder *QueryBuilder) buildUpdateFkUnchangedExpr( + tableDef *plan.TableDef, + fk *plan.ForeignKeyDef, + alias string, + selectNodeTag int32, + oldColName2Idx map[string]int32, + newColName2Idx map[string]int32, + selectNode *plan.Node, +) (*plan.Expr, error) { + colIDToName := make(map[uint64]string, len(tableDef.Cols)) + for _, col := range tableDef.Cols { + colIDToName[col.ColId] = col.Name + } + + var unchanged *plan.Expr + for _, childColID := range fk.Cols { + qualifiedName := alias + "." + colIDToName[childColID] + newPos, updated := newColName2Idx[qualifiedName] + if !updated { + continue + } + oldPos := oldColName2Idx[qualifiedName] + oldExpr := &plan.Expr{ + Typ: selectNode.ProjectList[oldPos].Typ, + Expr: &plan.Expr_Col{ + Col: &plan.ColRef{ + RelPos: selectNodeTag, + ColPos: oldPos, + }, + }, + } + newExpr := &plan.Expr{ + Typ: selectNode.ProjectList[newPos].Typ, + Expr: &plan.Expr_Col{ + Col: &plan.ColRef{ + RelPos: selectNodeTag, + ColPos: newPos, + }, + }, + } + equal, err := BindFuncExprImplByPlanExpr( + builder.GetContext(), + "<=>", + []*plan.Expr{oldExpr, newExpr}, + ) + if err != nil { + return nil, err + } + if unchanged == nil { + unchanged = equal + continue + } + unchanged, err = BindFuncExprImplByPlanExpr( + builder.GetContext(), + "and", + []*plan.Expr{unchanged, equal}, + ) + if err != nil { + return nil, err + } + } + return unchanged, nil +} diff --git a/pkg/sql/plan/build_test.go b/pkg/sql/plan/build_test.go index a00aa88758e5b..0008964a0870d 100644 --- a/pkg/sql/plan/build_test.go +++ b/pkg/sql/plan/build_test.go @@ -2068,21 +2068,21 @@ func TestUpdatePgStyleFromDedupPicksWholeSourceRow(t *testing.T) { } } -func TestUpdateFallbackPgStyleFromDedupPicksWholeSourceRow(t *testing.T) { +func TestUpdatePgStyleFromDedupFKTablePicksWholeSourceRow(t *testing.T) { mock := NewMockOptimizer(true) logicPlan, err := runOneStmt(mock, t, "UPDATE emp SET sal = dept.deptno, comm = dept.deptno FROM dept WHERE emp.deptno = dept.deptno") if err != nil { - t.Fatalf("build fallback UPDATE FROM plan: %v", err) + t.Fatalf("build UPDATE FROM plan: %v", err) } query := logicPlan.GetQuery() if hasUpdateFromDedupAnyValueAgg(query, len(mock.ctxt.tables["emp"].Cols)) { - t.Fatalf("fallback UPDATE FROM dedup must pick a whole source row, not aggregate each update column with any_value") + t.Fatalf("UPDATE FROM dedup must pick a whole source row, not aggregate each update column with any_value") } if !hasUpdateFromDedupWindow(query, 1) { - t.Fatalf("fallback UPDATE FROM dedup should use row_number window partitioned by target row_id") + t.Fatalf("UPDATE FROM dedup should use row_number window partitioned by target row_id") } } @@ -2111,11 +2111,10 @@ func TestUpdatePgStyleFromDedupPartitionsByRowIDNotGeometry32(t *testing.T) { } } -// TestUpdateFallbackPgStyleFromDedupPartitionsByRowIDNotGeometry32 guards the -// fallback (buildTableUpdate) path against the same GEOMETRY32 partition-key -// crash. emp has a foreign key, so UPDATE ... FROM routes through the fallback -// planner. -func TestUpdateFallbackPgStyleFromDedupPartitionsByRowIDNotGeometry32(t *testing.T) { +// TestUpdatePgStyleFromDedupFKTablePartitionsByRowIDNotGeometry32 guards the +// modern path for an FK-bearing target against the same GEOMETRY32 +// partition-key crash. +func TestUpdatePgStyleFromDedupFKTablePartitionsByRowIDNotGeometry32(t *testing.T) { mock := NewMockOptimizer(true) geoTyp := plan.Type{Id: int32(types.T_geometry32)} setMockColumnType(t, mock, "emp", "hiredate", geoTyp) @@ -2123,39 +2122,35 @@ func TestUpdateFallbackPgStyleFromDedupPartitionsByRowIDNotGeometry32(t *testing logicPlan, err := runOneStmt(mock, t, "UPDATE emp SET sal = dept.deptno, comm = dept.deptno FROM dept WHERE emp.deptno = dept.deptno") if err != nil { - t.Fatalf("build fallback UPDATE FROM with GEOMETRY32 column: %v", err) + t.Fatalf("build FK-table UPDATE FROM with GEOMETRY32 column: %v", err) } query := logicPlan.GetQuery() if !hasUpdateFromDedupWindow(query, 1) { - t.Fatalf("fallback UPDATE FROM dedup must partition by row_id, not by a GEOMETRY32 target column") + t.Fatalf("FK-table UPDATE FROM dedup must partition by row_id, not by a GEOMETRY32 target column") } if updateFromDedupPartitionsColName(query, "hiredate") { - t.Fatalf("fallback UPDATE FROM dedup must not include the GEOMETRY32 column in the partition key") + t.Fatalf("FK-table UPDATE FROM dedup must not include the GEOMETRY32 column in the partition key") } } -// TestUpdateFallbackPgStyleFromKeepsRowIdNullFilter guards the fallback path -// against losing the join-target NULL-row safeguard. The fallback path's -// needAggFilter drives both the any_value dedup AND an isnotnull(row_id) filter -// that drops NULL rows from left/right-join targets. Replacing the dedup with a -// row_number() window must still keep that NULL filter, otherwise a joined-target -// NULL row could leak into the update pipeline. -func TestUpdateFallbackPgStyleFromKeepsRowIdNullFilter(t *testing.T) { +// TestUpdatePgStyleFromFKTableUsesModernDedup guards the new FK-table route: +// unrelated child columns stay on the modern row_number dedup path. +func TestUpdatePgStyleFromFKTableUsesModernDedup(t *testing.T) { mock := NewMockOptimizer(true) logicPlan, err := runOneStmt(mock, t, "UPDATE emp SET sal = dept.deptno, comm = dept.deptno FROM dept WHERE emp.deptno = dept.deptno") if err != nil { - t.Fatalf("build fallback UPDATE FROM plan: %v", err) + t.Fatalf("build FK-table UPDATE FROM plan: %v", err) } query := logicPlan.GetQuery() if hasAnyValueAgg(query) { - t.Fatalf("fallback UPDATE FROM dedup must not use any_value aggregation") + t.Fatalf("modern FK-table UPDATE FROM dedup must not use any_value aggregation") } - if !hasRowIdIsNotNullFilter(query) { - t.Fatalf("fallback UPDATE FROM must keep the isnotnull(row_id) join-target NULL-row filter") + if !hasUpdateFromDedupWindow(query, 1) { + t.Fatalf("modern FK-table UPDATE FROM must use row_number partitioned by target row_id") } } @@ -2519,29 +2514,29 @@ func TestUpdateFallbackGeneratedColumnChainAfterOptimize(t *testing.T) { // but we verify the expression exists at the expected position. } -func TestUpdateFallbackGeneratedColumnDerivedTableSource(t *testing.T) { +func TestUpdateGeneratedColumnDerivedTableSourceOnFKTable(t *testing.T) { mock := NewMockOptimizer(true) setMockGeneratedColumn(t, mock, "emp", "sal", "comm") logicPlan, err := runOneStmt(mock, t, "UPDATE emp SET comm = 1, ename = 'derived-src-marker' FROM (SELECT deptno, loc FROM dept) AS d WHERE emp.deptno = d.deptno") if err != nil { - t.Fatalf("build fallback update with derived table source and generated column: %v", err) + t.Fatalf("build modern FK-table update with derived source and generated column: %v", err) } query := logicPlan.GetQuery() - empCols := len(mock.ctxt.tables["emp"].Cols) - expectedLen := empCols + 3 - node := requireFallbackSourceProjectNode(t, query, expectedLen, "derived-src-marker") - if node == nil { - t.Fatalf("missing source PROJECT with length %d and derived-src-marker", expectedLen) + hasMultiUpdate := false + for _, node := range query.Nodes { + if node.NodeType == plan.Node_MULTI_UPDATE { + hasMultiUpdate = true + break + } } - generatedPos := empCols + 2 - if generatedPos >= len(node.ProjectList) { - t.Fatalf("generated column position %d out of range (ProjectList length %d)", generatedPos, len(node.ProjectList)) + if !hasMultiUpdate { + t.Fatal("FK-table UPDATE with an unrelated child key must use MULTI_UPDATE") } - if node.ProjectList[generatedPos] == nil { - t.Fatalf("generated column expression at position %d should not be nil", generatedPos) + if !queryContainsStringLiteral(query, "derived-src-marker") { + t.Fatal("modern UPDATE must retain the derived-source assignment") } } @@ -2705,27 +2700,6 @@ func hasAnyValueAgg(query *Query) bool { return false } -// hasRowIdIsNotNullFilter reports whether the plan filters out joined-target -// NULL rows via isnotnull(row_id). This safeguard must survive on the fallback -// UPDATE ... FROM path even after duplicate matches are deduped by row_number(). -func hasRowIdIsNotNullFilter(query *Query) bool { - for _, node := range query.Nodes { - if node.NodeType != plan.Node_FILTER { - continue - } - for _, f := range node.FilterList { - fn := f.GetF() - if fn == nil || fn.Func.ObjName != "isnotnull" || len(fn.Args) != 1 { - continue - } - if exprContainsColName(fn.Args[0], catalog.Row_ID) { - return true - } - } - } - return false -} - // hasUpdateFromDedupWindow reports whether the plan contains a row_number window // used for UPDATE ... FROM dedup, partitioned on exactly partitionByLen row_id // columns. The dedup key must be the target row's physical identity (row_id), diff --git a/pkg/sql/plan/dml_context.go b/pkg/sql/plan/dml_context.go index ae3941b3cebd2..ffc5612d96b29 100644 --- a/pkg/sql/plan/dml_context.go +++ b/pkg/sql/plan/dml_context.go @@ -46,7 +46,7 @@ func NewDMLContext() *DMLContext { } func (dmlCtx *DMLContext) ResolveUpdateTables(ctx CompilerContext, stmt *tree.Update) error { - err := dmlCtx.ResolveTables(ctx, stmt.Tables, stmt.With, nil, false) + err := dmlCtx.resolveTables(ctx, stmt.Tables, stmt.With, nil, foreignKeyResolveDeferred) if err != nil { return classifyUpdateTableResolutionError(ctx, stmt, err) } @@ -132,6 +132,14 @@ func (dmlCtx *DMLContext) ResolveUpdateTables(ctx CompilerContext, stmt *tree.Up return nil } +type foreignKeyResolvePolicy uint8 + +const ( + foreignKeyResolveAlwaysReject foreignKeyResolvePolicy = iota + foreignKeyResolveRespectSession + foreignKeyResolveDeferred +) + func classifyUpdateTableResolutionError(ctx CompilerContext, stmt *tree.Update, err error) error { if !moerr.IsMoErrCode(err, moerr.ErrUnsupportedDML) { return err @@ -216,6 +224,20 @@ const unsupportedTableTypeDMLMsg = "unsupported DML: unsupported table type" const emptyTableNameDMLMsg = "unsupported DML: empty table name" func (dmlCtx *DMLContext) ResolveTables(ctx CompilerContext, tableExprs tree.TableExprs, with *tree.With, aliasMap map[string][2]string, respectFKCheck bool) error { + policy := foreignKeyResolveAlwaysReject + if respectFKCheck { + policy = foreignKeyResolveRespectSession + } + return dmlCtx.resolveTables(ctx, tableExprs, with, aliasMap, policy) +} + +func (dmlCtx *DMLContext) resolveTables( + ctx CompilerContext, + tableExprs tree.TableExprs, + with *tree.With, + aliasMap map[string][2]string, + foreignKeyPolicy foreignKeyResolvePolicy, +) error { cteMap := make(map[string]bool) if with != nil { for _, cte := range with.CTEs { @@ -224,7 +246,7 @@ func (dmlCtx *DMLContext) ResolveTables(ctx CompilerContext, tableExprs tree.Tab } for _, tbl := range tableExprs { - err := dmlCtx.ResolveSingleTable(ctx, tbl, aliasMap, cteMap, respectFKCheck) + err := dmlCtx.resolveSingleTable(ctx, tbl, aliasMap, cteMap, foreignKeyPolicy) if err != nil { return err } @@ -234,6 +256,20 @@ func (dmlCtx *DMLContext) ResolveTables(ctx CompilerContext, tableExprs tree.Tab } func (dmlCtx *DMLContext) ResolveSingleTable(ctx CompilerContext, tbl tree.TableExpr, aliasMap map[string][2]string, withMap map[string]bool, respectFKCheck bool) error { + policy := foreignKeyResolveAlwaysReject + if respectFKCheck { + policy = foreignKeyResolveRespectSession + } + return dmlCtx.resolveSingleTable(ctx, tbl, aliasMap, withMap, policy) +} + +func (dmlCtx *DMLContext) resolveSingleTable( + ctx CompilerContext, + tbl tree.TableExpr, + aliasMap map[string][2]string, + withMap map[string]bool, + foreignKeyPolicy foreignKeyResolvePolicy, +) error { var tblName, dbName, alias string if aliasTbl, ok := tbl.(*tree.AliasedTableExpr); ok { @@ -317,8 +353,8 @@ func (dmlCtx *DMLContext) ResolveSingleTable(ctx CompilerContext, tbl tree.Table return err } - checkFK := true - if respectFKCheck { + checkFK := foreignKeyPolicy == foreignKeyResolveAlwaysReject + if foreignKeyPolicy == foreignKeyResolveRespectSession { checkFK, err = IsForeignKeyChecksEnabled(ctx) if err != nil { return err diff --git a/pkg/sql/plan/update_planner_route_test.go b/pkg/sql/plan/update_planner_route_test.go index f717e12c62dfc..e7d5c480006f0 100644 --- a/pkg/sql/plan/update_planner_route_test.go +++ b/pkg/sql/plan/update_planner_route_test.go @@ -23,6 +23,7 @@ import ( "github.com/matrixorigin/matrixone/pkg/catalog" "github.com/matrixorigin/matrixone/pkg/common/moerr" "github.com/matrixorigin/matrixone/pkg/container/types" + "github.com/matrixorigin/matrixone/pkg/defines" planpb "github.com/matrixorigin/matrixone/pkg/pb/plan" "github.com/matrixorigin/matrixone/pkg/sql/parsers" "github.com/matrixorigin/matrixone/pkg/sql/parsers/dialect" @@ -177,15 +178,6 @@ func TestBindUpdateProducesTypedPlannerRoutes(t *testing.T) { wantRoute: updatePlannerLegacy, wantReason: updateRouteReasonMultiTarget, }, - { - name: "foreign key metadata", - sql: "UPDATE nation SET n_name = 'x'", - prepare: func(mock *MockOptimizer) { - mock.ctxt.tables["nation"].Fkeys = []*planpb.ForeignKeyDef{{Name: "fk"}} - }, - wantRoute: updatePlannerLegacy, - wantReason: updateRouteReasonForeignKey, - }, { name: "irregular index column", sql: "UPDATE nation SET n_comment = 'x'", @@ -249,3 +241,296 @@ func TestBindUpdateProducesTypedPlannerRoutes(t *testing.T) { }) } } + +func TestBindUpdateForeignKeyRoutingByAffectedColumns(t *testing.T) { + prepareEmpDept := func(mock *MockOptimizer) { + emp := mock.ctxt.tables["emp"] + dept := mock.ctxt.tables["dept"] + require.NotEmpty(t, emp.Fkeys) + emp.TblId = 88887 + emp.Fkeys[0].ForeignTbl = dept.TblId + emp.Fkeys[0].ForeignCols = []uint64{0} + dept.RefChildTbls = []uint64{emp.TblId} + mock.ctxt.id2name[emp.TblId] = "emp" + } + + t.Run("unrelated child column uses multi update without parent probe", func(t *testing.T) { + mock := NewMockOptimizer(true) + prepareEmpDept(mock) + logicPlan, err := runOneStmt(mock, t, "UPDATE emp SET sal = 1") + require.NoError(t, err) + + hasMultiUpdate := false + hasParentScan := false + parentID := mock.ctxt.tables["dept"].TblId + for _, node := range logicPlan.GetQuery().Nodes { + hasMultiUpdate = hasMultiUpdate || node.NodeType == planpb.Node_MULTI_UPDATE + hasParentScan = hasParentScan || + (node.NodeType == planpb.Node_TABLE_SCAN && + node.TableDef != nil && + node.TableDef.TblId == parentID) + } + require.True(t, hasMultiUpdate) + require.False(t, hasParentScan) + }) + + t.Run("nullable unique update on child table avoids legacy preinsert", func(t *testing.T) { + mock := NewMockOptimizer(true) + prepareEmpDept(mock) + logicPlan, err := runOneStmt(mock, t, "UPDATE emp SET ename = 'x'") + require.NoError(t, err) + + query := logicPlan.GetQuery() + require.Equal(t, 1, countUpdateFkPlanNodes(query, planpb.Node_MULTI_UPDATE)) + require.Equal(t, 0, countUpdateFkPlanNodes(query, planpb.Node_PRE_INSERT_UK)) + require.Equal(t, 0, countUpdateFkMarkJoins(query)) + }) + + t.Run("affected child column uses multi update with parent mark join", func(t *testing.T) { + mock := NewMockOptimizer(true) + prepareEmpDept(mock) + logicPlan, err := runOneStmt(mock, t, "UPDATE emp SET deptno = 2") + require.NoError(t, err) + + hasMultiUpdate := false + hasParentScan := false + hasMarkJoin := false + hasAssert := false + parentID := mock.ctxt.tables["dept"].TblId + for _, node := range logicPlan.GetQuery().Nodes { + hasMultiUpdate = hasMultiUpdate || node.NodeType == planpb.Node_MULTI_UPDATE + hasParentScan = hasParentScan || + (node.NodeType == planpb.Node_TABLE_SCAN && + node.TableDef != nil && + node.TableDef.TblId == parentID) + hasMarkJoin = hasMarkJoin || + (node.NodeType == planpb.Node_JOIN && node.JoinType == planpb.Node_MARK) + for _, filter := range node.FilterList { + if filter.GetF() != nil && filter.GetF().Func.ObjName == "assert" { + hasAssert = true + } + } + } + require.True(t, hasMultiUpdate) + require.True(t, hasParentScan) + require.True(t, hasMarkJoin) + require.True(t, hasAssert) + }) + + t.Run("affected referenced parent key keeps typed legacy route", func(t *testing.T) { + mock := NewMockOptimizer(true) + prepareEmpDept(mock) + + stmt, err := parsers.ParseOne( + mock.CurrentContext().GetContext(), + dialect.MYSQL, + "UPDATE dept SET deptno = 2", + 1, + ) + require.NoError(t, err) + defer stmt.Free() + + builder := NewQueryBuilder(planpb.Query_UPDATE, mock.CurrentContext(), false, true) + _, err = builder.bindUpdate(stmt.(*tree.Update), NewBindContext(builder, nil)) + require.Error(t, err) + route, reason, _ := classifyUpdatePlannerError(err) + require.Equal(t, updatePlannerLegacy, route) + require.Equal(t, updateRouteReasonForeignKey, reason) + }) + + t.Run("disabled checks skip child probe and parent fallback", func(t *testing.T) { + mock := NewMockOptimizer(true) + prepareEmpDept(mock) + mock.ctxt.SetContext(context.WithValue( + mock.ctxt.GetContext(), + defines.DisableFkCheck{}, + true, + )) + + for _, sql := range []string{ + "UPDATE emp SET deptno = 2", + "UPDATE dept SET deptno = 2", + } { + logicPlan, err := runOneStmt(mock, t, sql) + require.NoError(t, err) + require.Equal(t, 0, countUpdateFkMarkJoins(logicPlan.GetQuery())) + require.Equal(t, 1, countUpdateFkPlanNodes(logicPlan.GetQuery(), planpb.Node_MULTI_UPDATE)) + } + }) + + t.Run("unrelated referenced parent column stays modern", func(t *testing.T) { + mock := NewMockOptimizer(true) + prepareEmpDept(mock) + + logicPlan, err := runOneStmt(mock, t, "UPDATE dept SET loc = 'x'") + require.NoError(t, err) + require.Equal(t, 1, countUpdateFkPlanNodes(logicPlan.GetQuery(), planpb.Node_MULTI_UPDATE)) + require.Equal(t, 0, countUpdateFkMarkJoins(logicPlan.GetQuery())) + }) + + t.Run("only affected constraint is probed", func(t *testing.T) { + mock := NewMockOptimizer(true) + prepareEmpDept(mock) + emp := mock.ctxt.tables["emp"] + emp.Fkeys = append(emp.Fkeys, &planpb.ForeignKeyDef{ + Name: "fk_mgr", + Cols: []uint64{3}, + ForeignTbl: mock.ctxt.tables["dept"].TblId, + ForeignCols: []uint64{0}, + }) + + logicPlan, err := runOneStmt(mock, t, "UPDATE emp SET deptno = 2") + require.NoError(t, err) + require.Equal(t, 1, countUpdateFkMarkJoins(logicPlan.GetQuery())) + require.Equal(t, 1, countUpdateFkAsserts(logicPlan.GetQuery())) + require.True(t, updateFkPlanContainsFunc(logicPlan.GetQuery(), "<=>")) + }) +} + +func TestBindUpdateSelfReferencingForeignKeyRouting(t *testing.T) { + prepareSelfRef := func(mock *MockOptimizer) { + tableDef := mock.ctxt.tables["self_ref"] + tableDef.RefChildTbls = []uint64{tableDef.TblId} + mock.ctxt.id2name[tableDef.TblId] = "self_ref" + } + + t.Run("unrelated column stays modern without probe", func(t *testing.T) { + mock := NewMockOptimizer(true) + prepareSelfRef(mock) + logicPlan, err := runOneStmt(mock, t, "UPDATE self_ref SET name = 'x'") + require.NoError(t, err) + require.Equal(t, 1, countUpdateFkPlanNodes(logicPlan.GetQuery(), planpb.Node_MULTI_UPDATE)) + require.Equal(t, 0, countUpdateFkMarkJoins(logicPlan.GetQuery())) + }) + + t.Run("child key uses self mark probe", func(t *testing.T) { + mock := NewMockOptimizer(true) + prepareSelfRef(mock) + logicPlan, err := runOneStmt(mock, t, "UPDATE self_ref SET parent_id = 2") + require.NoError(t, err) + require.Equal(t, 1, countUpdateFkPlanNodes(logicPlan.GetQuery(), planpb.Node_MULTI_UPDATE)) + require.Equal(t, 1, countUpdateFkMarkJoins(logicPlan.GetQuery())) + require.Equal(t, 1, countUpdateFkAsserts(logicPlan.GetQuery())) + }) + + t.Run("referenced key keeps typed legacy route", func(t *testing.T) { + mock := NewMockOptimizer(true) + prepareSelfRef(mock) + stmt, err := parsers.ParseOne( + mock.CurrentContext().GetContext(), + dialect.MYSQL, + "UPDATE self_ref SET id = 2", + 1, + ) + require.NoError(t, err) + defer stmt.Free() + + builder := NewQueryBuilder(planpb.Query_UPDATE, mock.CurrentContext(), false, true) + _, err = builder.bindUpdate(stmt.(*tree.Update), NewBindContext(builder, nil)) + require.Error(t, err) + route, reason, _ := classifyUpdatePlannerError(err) + require.Equal(t, updatePlannerLegacy, route) + require.Equal(t, updateRouteReasonForeignKey, reason) + }) +} + +func TestResolveSingleTablePreservesForeignKeyPolicy(t *testing.T) { + mock := NewMockOptimizer(true) + stmt, err := parsers.ParseOne( + mock.CurrentContext().GetContext(), + dialect.MYSQL, + "UPDATE emp SET sal = 1", + 1, + ) + require.NoError(t, err) + defer stmt.Free() + + tableExpr := stmt.(*tree.Update).Tables[0] + dmlCtx := NewDMLContext() + require.Error(t, dmlCtx.ResolveSingleTable( + mock.CurrentContext(), + tableExpr, + nil, + map[string]bool{}, + false, + )) + + mock.ctxt.SetContext(context.WithValue( + mock.ctxt.GetContext(), + defines.DisableFkCheck{}, + true, + )) + require.NoError(t, NewDMLContext().ResolveSingleTable( + mock.CurrentContext(), + tableExpr, + nil, + map[string]bool{}, + true, + )) +} + +func countUpdateFkPlanNodes(query *planpb.Query, nodeType planpb.Node_NodeType) int { + count := 0 + for _, node := range query.Nodes { + if node.NodeType == nodeType { + count++ + } + } + return count +} + +func countUpdateFkMarkJoins(query *planpb.Query) int { + count := 0 + for _, node := range query.Nodes { + if node.NodeType == planpb.Node_JOIN && node.JoinType == planpb.Node_MARK { + count++ + } + } + return count +} + +func countUpdateFkAsserts(query *planpb.Query) int { + count := 0 + for _, node := range query.Nodes { + for _, filter := range node.FilterList { + if filter.GetF() != nil && filter.GetF().Func.ObjName == "assert" { + count++ + } + } + } + return count +} + +func updateFkPlanContainsFunc(query *planpb.Query, name string) bool { + var contains func(*planpb.Expr) bool + contains = func(expr *planpb.Expr) bool { + fn := expr.GetF() + if fn == nil { + return false + } + if fn.Func.ObjName == name { + return true + } + for _, arg := range fn.Args { + if contains(arg) { + return true + } + } + return false + } + + for _, node := range query.Nodes { + for _, exprs := range [][]*planpb.Expr{ + node.ProjectList, + node.FilterList, + node.OnList, + } { + for _, expr := range exprs { + if contains(expr) { + return true + } + } + } + } + return false +} diff --git a/test/distributed/cases/foreign_key/fk_self_refer.result b/test/distributed/cases/foreign_key/fk_self_refer.result index 2302d189aa22c..f4a74bdd8f2ea 100644 --- a/test/distributed/cases/foreign_key/fk_self_refer.result +++ b/test/distributed/cases/foreign_key/fk_self_refer.result @@ -39,7 +39,7 @@ internal error: Cannot delete or update a parent row: a foreign key constraint f update t1 set b=NULL where a= 4; constraint violation: Column 'b' cannot be null update t1 set b=5 where a= 4; -Cannot add or update a child row: a foreign key constraint fails +internal error: Cannot add or update a child row: a foreign key constraint fails insert into t1 values (3,4); update t1 set b = 3 where a= 4; select * from t1; diff --git a/test/distributed/cases/foreign_key/fk_self_refer2.result b/test/distributed/cases/foreign_key/fk_self_refer2.result index 00347e527c3dc..7639398e740e4 100644 --- a/test/distributed/cases/foreign_key/fk_self_refer2.result +++ b/test/distributed/cases/foreign_key/fk_self_refer2.result @@ -79,7 +79,7 @@ delete from t1 where a = 4; delete from t1 where a = 6; update t1 set b = 8 where a = 3; update t1 set b = 6 where a = 3; -Cannot add or update a child row: a foreign key constraint fails +internal error: Cannot add or update a child row: a foreign key constraint fails delete from t1 where a = 8; internal error: Cannot delete or update a parent row: a foreign key constraint fails delete from t1 where a = 3; diff --git a/test/distributed/cases/foreign_key/fk_self_refer3.result b/test/distributed/cases/foreign_key/fk_self_refer3.result index 7369f60f7b940..c06ba0f73d32f 100644 --- a/test/distributed/cases/foreign_key/fk_self_refer3.result +++ b/test/distributed/cases/foreign_key/fk_self_refer3.result @@ -24,13 +24,13 @@ insert into t1 values (1,4,3,1,4,1,3); update t1 set b = 5 where b = 4; internal error: Cannot delete or update a parent row: a foreign key constraint fails update t1 set e = 5 where b = 4; -Cannot add or update a child row: a foreign key constraint fails +internal error: Cannot add or update a child row: a foreign key constraint fails update t1 set e = NULL where b = 4; update t1 set b = 5 where b = 4; update t1 set c = 4 where b = 5; internal error: Cannot delete or update a parent row: a foreign key constraint fails update t1 set g = 4 where b = 5; -Cannot add or update a child row: a foreign key constraint fails +internal error: Cannot add or update a child row: a foreign key constraint fails update t1 set g = 2 where b = 5; update t1 set c = 4 where b = 5; delete from t1 where b = 5; diff --git a/test/distributed/cases/foreign_key/update_modern_fk.result b/test/distributed/cases/foreign_key/update_modern_fk.result new file mode 100644 index 0000000000000..077e66305ff43 --- /dev/null +++ b/test/distributed/cases/foreign_key/update_modern_fk.result @@ -0,0 +1,96 @@ +drop database if exists update_modern_fk; +create database update_modern_fk; +use update_modern_fk; +create table parent_single ( +id int primary key +); +create table child_single ( +id int primary key, +parent_id int, +note varchar(32), +unique key uk_note(note), +constraint fk_single foreign key (parent_id) references parent_single(id) +); +insert into parent_single values (1), (2), (3); +insert into child_single values (10, 1, 'a'), (20, 2, null); +update child_single set note = 'b' where id = 10; +insert into child_single values (30, 1, 'a'); +delete from child_single where id = 30; +update child_single set parent_id = 3 where id = 10; +select * from child_single order by id; +id parent_id note +10 3 b +20 2 NULL +update child_single set parent_id = 99 where id = 10; +internal error: Cannot add or update a child row: a foreign key constraint fails +select * from child_single order by id; +id parent_id note +10 3 b +20 2 NULL +set foreign_key_checks = 0; +update child_single set parent_id = 99 where id = 10; +set foreign_key_checks = 1; +update child_single set parent_id = parent_id where id = 10; +update child_single set note = 'orphan' where id = 10; +insert into child_single values (30, 1, 'orphan'); +Duplicate entry 'orphan' for key 'note' +select * from child_single order by id; +id parent_id note +10 99 orphan +20 2 NULL +update child_single set parent_id = 1 where id = 10; +update child_single set parent_id = case id when 10 then 2 else 98 end; +internal error: Cannot add or update a child row: a foreign key constraint fails +select * from child_single order by id; +id parent_id note +10 1 orphan +20 2 NULL +create table parent_composite ( +a int, +b int, +primary key (a, b) +); +create table child_composite ( +id int primary key, +a int, +b int, +constraint fk_composite foreign key (a, b) references parent_composite(a, b) +); +insert into parent_composite values (1, 1), (2, 2); +insert into child_composite values (1, 1, 1), (2, 2, 2), (3, null, null); +update child_composite set a = null, b = 99 where id = 1; +update child_composite set a = 99, b = null where id = 2; +update child_composite set a = 1, b = 1 where id = 3; +select * from child_composite order by id; +id a b +1 NULL 99 +2 99 NULL +3 1 1 +update child_composite set a = 98, b = 98 where id = 3; +internal error: Cannot add or update a child row: a foreign key constraint fails +select * from child_composite order by id; +id a b +1 NULL 99 +2 99 NULL +3 1 1 +create table self_ref ( +id int primary key, +parent_id int, +name varchar(32), +constraint fk_self foreign key (parent_id) references self_ref(id) +); +insert into self_ref values (1, 1, 'root'), (2, 1, 'child'); +update self_ref set name = 'changed' where id = 2; +update self_ref set parent_id = 2 where id = 2; +update self_ref set parent_id = 99 where id = 2; +internal error: Cannot add or update a child row: a foreign key constraint fails +select * from self_ref order by id; +id parent_id name +1 1 root +2 2 changed +drop table self_ref; +drop table child_composite; +drop table parent_composite; +drop table child_single; +drop table parent_single; +drop database update_modern_fk; diff --git a/test/distributed/cases/foreign_key/update_modern_fk.sql b/test/distributed/cases/foreign_key/update_modern_fk.sql new file mode 100644 index 0000000000000..83eda70ace224 --- /dev/null +++ b/test/distributed/cases/foreign_key/update_modern_fk.sql @@ -0,0 +1,83 @@ +drop database if exists update_modern_fk; +create database update_modern_fk; +use update_modern_fk; + +create table parent_single ( + id int primary key +); + +create table child_single ( + id int primary key, + parent_id int, + note varchar(32), + unique key uk_note(note), + constraint fk_single foreign key (parent_id) references parent_single(id) +); + +insert into parent_single values (1), (2), (3); +insert into child_single values (10, 1, 'a'), (20, 2, null); + +update child_single set note = 'b' where id = 10; +insert into child_single values (30, 1, 'a'); +delete from child_single where id = 30; +update child_single set parent_id = 3 where id = 10; +select * from child_single order by id; + +update child_single set parent_id = 99 where id = 10; +select * from child_single order by id; + +set foreign_key_checks = 0; +update child_single set parent_id = 99 where id = 10; +set foreign_key_checks = 1; +update child_single set parent_id = parent_id where id = 10; +update child_single set note = 'orphan' where id = 10; +insert into child_single values (30, 1, 'orphan'); +select * from child_single order by id; + +update child_single set parent_id = 1 where id = 10; +update child_single set parent_id = case id when 10 then 2 else 98 end; +select * from child_single order by id; + +create table parent_composite ( + a int, + b int, + primary key (a, b) +); + +create table child_composite ( + id int primary key, + a int, + b int, + constraint fk_composite foreign key (a, b) references parent_composite(a, b) +); + +insert into parent_composite values (1, 1), (2, 2); +insert into child_composite values (1, 1, 1), (2, 2, 2), (3, null, null); + +update child_composite set a = null, b = 99 where id = 1; +update child_composite set a = 99, b = null where id = 2; +update child_composite set a = 1, b = 1 where id = 3; +select * from child_composite order by id; + +update child_composite set a = 98, b = 98 where id = 3; +select * from child_composite order by id; + +create table self_ref ( + id int primary key, + parent_id int, + name varchar(32), + constraint fk_self foreign key (parent_id) references self_ref(id) +); + +insert into self_ref values (1, 1, 'root'), (2, 1, 'child'); +update self_ref set name = 'changed' where id = 2; +update self_ref set parent_id = 2 where id = 2; +update self_ref set parent_id = 99 where id = 2; +select * from self_ref order by id; + +drop table self_ref; +drop table child_composite; +drop table parent_composite; +drop table child_single; +drop table parent_single; +drop database update_modern_fk; From c32538b011875a25514a9b9c647ef9a465b93614 Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Thu, 30 Jul 2026 11:21:33 +0800 Subject: [PATCH 02/22] ci: rerun arm64 SCA test From 91b00f75989b286570d76896df3b68151cb26efd Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Thu, 30 Jul 2026 14:15:17 +0800 Subject: [PATCH 03/22] plan: move parent FK update actions to multi update --- pkg/pb/plan/plan.pb.go | 1722 +++++++++-------- .../colexec/multi_update/affect_rows_test.go | 8 + .../multi_update/multi_update_partition.go | 1 + pkg/sql/colexec/multi_update/types.go | 6 +- pkg/sql/compile/operator.go | 1 + pkg/sql/compile/remoterun.go | 2 + pkg/sql/compile/remoterun_test.go | 9 +- pkg/sql/plan/bind_update_fk.go | 837 +++++++- pkg/sql/plan/deepcopy.go | 1 + pkg/sql/plan/update_planner_route_test.go | 95 +- proto/plan.proto | 1 + .../cases/foreign_key/update_modern_fk.result | 196 +- .../cases/foreign_key/update_modern_fk.sql | 112 ++ 13 files changed, 2092 insertions(+), 899 deletions(-) diff --git a/pkg/pb/plan/plan.pb.go b/pkg/pb/plan/plan.pb.go index b426ab074aba7..db7a7d0cade11 100644 --- a/pkg/pb/plan/plan.pb.go +++ b/pkg/pb/plan/plan.pb.go @@ -5822,6 +5822,7 @@ type UpdateCtx struct { SkipInsertOnNullPk bool `protobuf:"varint,10,opt,name=skip_insert_on_null_pk,json=skipInsertOnNullPk,proto3" json:"skip_insert_on_null_pk,omitempty"` InsertPkColIdx int32 `protobuf:"varint,11,opt,name=insert_pk_col_idx,json=insertPkColIdx,proto3" json:"insert_pk_col_idx,omitempty"` CountDeleteAffectRows bool `protobuf:"varint,12,opt,name=count_delete_affect_rows,json=countDeleteAffectRows,proto3" json:"count_delete_affect_rows,omitempty"` + IgnoreAffectedRows bool `protobuf:"varint,13,opt,name=ignore_affected_rows,json=ignoreAffectedRows,proto3" json:"ignore_affected_rows,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -5916,6 +5917,13 @@ func (m *UpdateCtx) GetCountDeleteAffectRows() bool { return false } +func (m *UpdateCtx) GetIgnoreAffectedRows() bool { + if m != nil { + return m.IgnoreAffectedRows + } + return false +} + type InsertCtx struct { Ref *ObjectRef `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` AddAffectedRows bool `protobuf:"varint,2,opt,name=add_affected_rows,json=addAffectedRows,proto3" json:"add_affected_rows,omitempty"` @@ -13949,852 +13957,853 @@ func init() { func init() { proto.RegisterFile("plan.proto", fileDescriptor_2d655ab2f7683c23) } var fileDescriptor_2d655ab2f7683c23 = []byte{ - // 13511 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0xbd, 0x5b, 0x8c, 0x23, 0x47, - 0x96, 0x18, 0x5a, 0x2c, 0xbe, 0x0f, 0xc9, 0xaa, 0xac, 0xe8, 0x17, 0xbb, 0xd5, 0xea, 0x2e, 0x65, - 0xb7, 0x5a, 0xad, 0x96, 0xd4, 0x92, 0xba, 0xf5, 0x9c, 0x9d, 0xdd, 0x19, 0x16, 0xc9, 0xaa, 0xa2, - 0xc4, 0x22, 0x6b, 0x92, 0xac, 0x6e, 0x69, 0xf6, 0x2e, 0x12, 0x59, 0xcc, 0x24, 0x2b, 0x55, 0xc9, - 0x4c, 0x2a, 0x33, 0xd9, 0x55, 0x35, 0xc0, 0xe2, 0x0e, 0xee, 0xc7, 0x3e, 0x80, 0xfb, 0x77, 0x71, - 0xef, 0xfd, 0x5a, 0x78, 0x6d, 0xc0, 0x30, 0x60, 0xd8, 0x30, 0x6c, 0x0c, 0xb0, 0xde, 0x3f, 0x0f, - 0xfc, 0xb3, 0x86, 0x1f, 0x30, 0x6c, 0xd8, 0x8b, 0xb5, 0x81, 0xb5, 0x3d, 0xfe, 0xb5, 0x61, 0x03, - 0x36, 0x60, 0xc0, 0xfe, 0xb0, 0x71, 0x4e, 0x44, 0x66, 0x46, 0x92, 0xec, 0x6e, 0x49, 0x33, 0x0b, - 0xf8, 0xa7, 0x8a, 0x71, 0x1e, 0xf1, 0x8e, 0x13, 0xe7, 0x9c, 0x38, 0x11, 0x09, 0x30, 0x73, 0x0c, - 0xf7, 0xe1, 0xcc, 0xf7, 0x42, 0x8f, 0xe5, 0xf0, 0xf7, 0x8d, 0x77, 0x26, 0x76, 0x78, 0x32, 0x3f, - 0x7e, 0x38, 0xf2, 0xa6, 0xef, 0x4e, 0xbc, 0x89, 0xf7, 0x2e, 0x21, 0x8f, 0xe7, 0x63, 0x4a, 0x51, - 0x82, 0x7e, 0x71, 0xa6, 0x1b, 0xe0, 0x78, 0xa3, 0x53, 0xf1, 0x7b, 0x33, 0xb4, 0xa7, 0x56, 0x10, - 0x1a, 0xd3, 0x19, 0x07, 0xa8, 0x7f, 0x94, 0x81, 0xdc, 0xf0, 0x62, 0x66, 0xb1, 0x0d, 0x58, 0xb7, - 0xcd, 0x7a, 0x66, 0x3b, 0x73, 0x3f, 0xaf, 0xad, 0xdb, 0x26, 0xdb, 0x86, 0x8a, 0xeb, 0x85, 0xbd, - 0xb9, 0xe3, 0x18, 0xc7, 0x8e, 0x55, 0x5f, 0xdf, 0xce, 0xdc, 0x2f, 0x69, 0x32, 0x88, 0xbd, 0x02, - 0x65, 0x63, 0x1e, 0x7a, 0xba, 0xed, 0x8e, 0xfc, 0x7a, 0x96, 0xf0, 0x25, 0x04, 0x74, 0xdc, 0x91, - 0xcf, 0x2e, 0x43, 0xfe, 0xcc, 0x36, 0xc3, 0x93, 0x7a, 0x8e, 0x72, 0xe4, 0x09, 0x84, 0x06, 0x23, - 0xc3, 0xb1, 0xea, 0x79, 0x0e, 0xa5, 0x04, 0x42, 0x43, 0x2a, 0xa4, 0xb0, 0x9d, 0xb9, 0x5f, 0xd6, - 0x78, 0x82, 0xdd, 0x02, 0xb0, 0xdc, 0xf9, 0xf4, 0x99, 0xe1, 0xcc, 0xad, 0xa0, 0x5e, 0x24, 0x94, - 0x04, 0x51, 0x7f, 0x00, 0xe5, 0x69, 0x30, 0xd9, 0xb7, 0x0c, 0xd3, 0xf2, 0xd9, 0x35, 0x28, 0x4e, - 0x83, 0x89, 0x1e, 0x1a, 0x13, 0xd1, 0x84, 0xc2, 0x34, 0x98, 0x0c, 0x8d, 0x09, 0xbb, 0x0e, 0x25, - 0x42, 0x5c, 0xcc, 0x78, 0x1b, 0xf2, 0x1a, 0x12, 0x62, 0x8b, 0xd5, 0xdf, 0x2f, 0x40, 0xb1, 0x6b, - 0x87, 0x96, 0x6f, 0x38, 0xec, 0x2a, 0x14, 0xec, 0xc0, 0x9d, 0x3b, 0x0e, 0xb1, 0x97, 0x34, 0x91, - 0x62, 0x57, 0x21, 0x6f, 0x7f, 0xf2, 0xcc, 0x70, 0x38, 0xef, 0xfe, 0x9a, 0xc6, 0x93, 0xac, 0x0e, - 0x05, 0xfb, 0xfd, 0x8f, 0x10, 0x91, 0x15, 0x08, 0x91, 0x26, 0xcc, 0xe3, 0x47, 0x88, 0xc9, 0xc5, - 0x18, 0x4a, 0x13, 0xe6, 0xa3, 0x0f, 0x10, 0x83, 0xad, 0xcf, 0x12, 0x86, 0xd2, 0x58, 0xca, 0x9c, - 0x4a, 0xc1, 0x0e, 0xa8, 0x61, 0x29, 0xf3, 0xa8, 0x94, 0x39, 0x2f, 0xa5, 0x28, 0x10, 0x22, 0x4d, - 0x18, 0x5e, 0x4a, 0x29, 0xc6, 0xc4, 0xa5, 0xcc, 0x79, 0x29, 0xe5, 0xed, 0xcc, 0xfd, 0x1c, 0x61, - 0x78, 0x29, 0x97, 0x21, 0x67, 0x22, 0x1c, 0xb6, 0x33, 0xf7, 0x33, 0xfb, 0x6b, 0x1a, 0xa5, 0x10, - 0x1a, 0x20, 0xb4, 0x82, 0x1d, 0x8c, 0xd0, 0x40, 0x40, 0x8f, 0x11, 0x5a, 0xc5, 0xde, 0x40, 0xe8, - 0xb1, 0x80, 0x8e, 0x11, 0x5a, 0xdb, 0xce, 0xdc, 0x5f, 0x47, 0x28, 0xa6, 0xd8, 0x0d, 0x28, 0x9a, - 0x46, 0x68, 0x21, 0x62, 0x43, 0x34, 0x39, 0x02, 0x20, 0x0e, 0x67, 0x1c, 0xe2, 0x36, 0x45, 0xa3, - 0x23, 0x00, 0x53, 0xa1, 0x82, 0x64, 0x11, 0x5e, 0x11, 0x78, 0x19, 0xc8, 0x3e, 0x84, 0xaa, 0x69, - 0x8d, 0xec, 0xa9, 0xe1, 0xf0, 0x36, 0x6d, 0x6d, 0x67, 0xee, 0x57, 0x1e, 0x6d, 0x3e, 0xa4, 0x35, - 0x11, 0x63, 0xf6, 0xd7, 0xb4, 0x14, 0x19, 0xfb, 0x04, 0x6a, 0x22, 0xfd, 0xfe, 0x23, 0xea, 0x58, - 0x46, 0x7c, 0x4a, 0x8a, 0xef, 0xfd, 0x47, 0x9f, 0xec, 0xaf, 0x69, 0x69, 0x42, 0x76, 0x17, 0xaa, - 0xf1, 0x12, 0x41, 0xc6, 0x4b, 0xa2, 0x56, 0x29, 0x28, 0x36, 0xeb, 0xab, 0xc0, 0x73, 0x91, 0xe0, - 0xb2, 0xe8, 0xb7, 0x08, 0xc0, 0xb6, 0x01, 0x4c, 0x6b, 0x6c, 0xcc, 0x9d, 0x10, 0xd1, 0x57, 0x44, - 0x07, 0x4a, 0x30, 0x76, 0x0b, 0xca, 0xf3, 0x19, 0xb6, 0xf2, 0x89, 0xe1, 0xd4, 0xaf, 0x0a, 0x82, - 0x04, 0x84, 0xb9, 0xe3, 0x3c, 0x47, 0xec, 0x35, 0x31, 0xba, 0x11, 0x00, 0x87, 0xf7, 0x99, 0x35, - 0x42, 0x54, 0x5d, 0x14, 0x2c, 0xd2, 0xb8, 0x8a, 0xec, 0x60, 0xc7, 0x76, 0xeb, 0xd7, 0x69, 0x06, - 0xf3, 0x04, 0xbb, 0x09, 0xd9, 0xc0, 0x1f, 0xd5, 0x6f, 0x50, 0xfb, 0x81, 0xb7, 0xbf, 0x7d, 0x3e, - 0xf3, 0x35, 0x04, 0xef, 0x14, 0x21, 0x4f, 0xab, 0x49, 0xbd, 0x09, 0xa5, 0x43, 0xc3, 0x37, 0xa6, - 0x9a, 0x35, 0x66, 0x0a, 0x64, 0x67, 0x5e, 0x20, 0xd6, 0x11, 0xfe, 0x54, 0xbb, 0x50, 0x78, 0x62, - 0xf8, 0x88, 0x63, 0x90, 0x73, 0x8d, 0xa9, 0x45, 0xc8, 0xb2, 0x46, 0xbf, 0x71, 0xed, 0x04, 0x17, - 0x41, 0x68, 0x4d, 0x85, 0x90, 0x10, 0x29, 0x84, 0x4f, 0x1c, 0xef, 0x58, 0xac, 0x91, 0x92, 0x26, - 0x52, 0xea, 0xff, 0x95, 0x81, 0x42, 0xd3, 0x73, 0x30, 0xbb, 0x6b, 0x50, 0xf4, 0x2d, 0x47, 0x4f, - 0x8a, 0x2b, 0xf8, 0x96, 0x73, 0xe8, 0x05, 0x88, 0x18, 0x79, 0x1c, 0xc1, 0x57, 0x6d, 0x61, 0xe4, - 0x11, 0x22, 0xaa, 0x40, 0x56, 0xaa, 0xc0, 0x75, 0x28, 0x85, 0xc7, 0x8e, 0x4e, 0xf0, 0x1c, 0xc1, - 0x8b, 0xe1, 0xb1, 0xd3, 0x43, 0xd4, 0x35, 0x28, 0x9a, 0xc7, 0x1c, 0x93, 0x27, 0x4c, 0xc1, 0x3c, - 0x46, 0x84, 0xfa, 0x29, 0x94, 0x35, 0xe3, 0x4c, 0x54, 0xe3, 0x0a, 0x14, 0x30, 0x03, 0x21, 0xff, - 0x72, 0x5a, 0x3e, 0x3c, 0x76, 0x3a, 0x26, 0x82, 0xb1, 0x12, 0xb6, 0x49, 0x75, 0xc8, 0x69, 0xf9, - 0x91, 0xe7, 0x74, 0x4c, 0x75, 0x08, 0xd0, 0xf4, 0x7c, 0xff, 0x3b, 0x37, 0xe1, 0x32, 0xe4, 0x4d, - 0x6b, 0x16, 0x9e, 0x70, 0xd1, 0xa1, 0xf1, 0x84, 0xfa, 0x00, 0x4a, 0x38, 0x2e, 0x5d, 0x3b, 0x08, - 0xd9, 0x2d, 0xc8, 0x39, 0x76, 0x10, 0xd6, 0x33, 0xdb, 0xd9, 0x85, 0x51, 0x23, 0xb8, 0xba, 0x0d, - 0xa5, 0x03, 0xe3, 0xfc, 0x09, 0x8e, 0x1c, 0xe6, 0x46, 0x43, 0x28, 0x86, 0x44, 0x8c, 0x67, 0x15, - 0x60, 0x68, 0xf8, 0x13, 0x2b, 0x24, 0x49, 0xf7, 0x5f, 0x32, 0x50, 0x19, 0xcc, 0x8f, 0xbf, 0x9e, - 0x5b, 0xfe, 0x05, 0xd6, 0xf9, 0x3e, 0x64, 0xc3, 0x8b, 0x19, 0x71, 0x6c, 0x3c, 0xba, 0xca, 0xb3, - 0x97, 0xf0, 0x0f, 0x91, 0x49, 0x43, 0x12, 0x6c, 0x84, 0xeb, 0x99, 0x56, 0xd4, 0x07, 0x79, 0xad, - 0x80, 0xc9, 0x8e, 0x89, 0xdb, 0x85, 0x37, 0x13, 0xa3, 0xb0, 0xee, 0xcd, 0xd8, 0x36, 0xe4, 0x47, - 0x27, 0xb6, 0x63, 0xd2, 0x00, 0xa4, 0xeb, 0xcc, 0x11, 0x38, 0x4a, 0xbe, 0x77, 0xa6, 0x07, 0xf6, - 0x4f, 0x22, 0xf1, 0x5f, 0xf4, 0xbd, 0xb3, 0x81, 0xfd, 0x13, 0x4b, 0x1d, 0x8a, 0x3d, 0x08, 0xa0, - 0x30, 0x68, 0x36, 0xba, 0x0d, 0x4d, 0x59, 0xc3, 0xdf, 0xed, 0x2f, 0x3a, 0x83, 0xe1, 0x40, 0xc9, - 0xb0, 0x0d, 0x80, 0x5e, 0x7f, 0xa8, 0x8b, 0xf4, 0x3a, 0x2b, 0xc0, 0x7a, 0xa7, 0xa7, 0x64, 0x91, - 0x06, 0xe1, 0x9d, 0x9e, 0x92, 0x63, 0x45, 0xc8, 0x36, 0x7a, 0x5f, 0x2a, 0x79, 0xfa, 0xd1, 0xed, - 0x2a, 0x05, 0xf5, 0xbf, 0xaf, 0x43, 0xb9, 0x7f, 0xfc, 0x95, 0x35, 0x0a, 0xb1, 0xcd, 0x38, 0x4b, - 0x2d, 0xff, 0x99, 0xe5, 0x53, 0xb3, 0xb3, 0x9a, 0x48, 0x61, 0x43, 0xcc, 0x63, 0x6a, 0x5c, 0x56, - 0x5b, 0x37, 0x8f, 0x89, 0x6e, 0x74, 0x62, 0x4d, 0x0d, 0x6a, 0x1c, 0xd2, 0x51, 0x0a, 0x57, 0x85, - 0x77, 0xfc, 0x15, 0x35, 0x2f, 0xab, 0xe1, 0x4f, 0x76, 0x1b, 0x2a, 0x3c, 0x0f, 0x79, 0x7e, 0x01, - 0x07, 0x2d, 0x4e, 0xbe, 0x82, 0x3c, 0xf9, 0x88, 0x93, 0x72, 0xe5, 0x48, 0xb1, 0xb7, 0x71, 0x50, - 0x4f, 0xcc, 0x68, 0xef, 0xf8, 0x2b, 0x8e, 0x2d, 0xf1, 0x19, 0xed, 0x1d, 0x7f, 0x45, 0xa8, 0xb7, - 0x60, 0x2b, 0x98, 0x1f, 0x07, 0x23, 0xdf, 0x9e, 0x85, 0xb6, 0xe7, 0x72, 0x9a, 0x32, 0xd1, 0x28, - 0x32, 0x82, 0x88, 0xef, 0x43, 0x69, 0x36, 0x3f, 0xd6, 0x6d, 0x77, 0xec, 0x91, 0xd8, 0xaf, 0x3c, - 0xaa, 0xf1, 0x81, 0x39, 0x9c, 0x1f, 0x77, 0xdc, 0xb1, 0xa7, 0x15, 0x67, 0xfc, 0x07, 0x53, 0xa1, - 0xe6, 0x7a, 0xa1, 0x8e, 0xaa, 0x82, 0x3e, 0xb5, 0x42, 0x83, 0xf6, 0x03, 0xbe, 0xe1, 0x77, 0xbd, - 0xd1, 0xe9, 0x81, 0x15, 0x1a, 0xec, 0x01, 0x94, 0x02, 0xd7, 0x98, 0x05, 0x27, 0x5e, 0x48, 0x1b, - 0x43, 0xe5, 0xd1, 0x86, 0x98, 0x3b, 0x02, 0xaa, 0xc5, 0x78, 0xf5, 0x1e, 0x14, 0x45, 0x19, 0xa8, - 0x27, 0x84, 0x96, 0x6b, 0xb8, 0xa1, 0x1e, 0x2b, 0x18, 0x25, 0x0e, 0xe8, 0x98, 0xea, 0xcf, 0x32, - 0xa0, 0x0c, 0xa4, 0x6a, 0x53, 0x41, 0xab, 0xa4, 0xcc, 0xab, 0x00, 0xc6, 0x68, 0xe4, 0xcd, 0x79, - 0x36, 0x7c, 0x32, 0x96, 0x05, 0xa4, 0x63, 0xca, 0x7d, 0x9d, 0x4d, 0xf5, 0xf5, 0x6b, 0x50, 0x8d, - 0xf8, 0x24, 0x01, 0x51, 0x11, 0xb0, 0xa8, 0xb7, 0x83, 0x79, 0x4a, 0x4a, 0x14, 0x83, 0x39, 0xe7, - 0xbe, 0x0a, 0x05, 0xd2, 0x46, 0x82, 0x68, 0x04, 0x79, 0x4a, 0xfd, 0xd3, 0x0c, 0xd4, 0x3a, 0xae, - 0x69, 0x9d, 0x0f, 0x46, 0x86, 0x1b, 0x75, 0xa0, 0x1d, 0xe8, 0x36, 0xc2, 0xf4, 0x60, 0x64, 0xb8, - 0x42, 0x91, 0xa8, 0xd8, 0x41, 0x4c, 0x87, 0x6d, 0xe0, 0x04, 0x54, 0xd4, 0x3a, 0xe5, 0x58, 0x26, - 0x08, 0x15, 0x76, 0x0f, 0x36, 0x8f, 0x2d, 0xc7, 0x73, 0x27, 0x7a, 0xe8, 0xe9, 0x5c, 0x23, 0xe2, - 0x6d, 0xa9, 0x71, 0xf0, 0xd0, 0x1b, 0x92, 0x66, 0x74, 0x19, 0xf2, 0x33, 0xc3, 0x0f, 0x83, 0x7a, - 0x6e, 0x3b, 0x8b, 0x4b, 0x9e, 0x12, 0xd8, 0xcd, 0x76, 0xa0, 0xcf, 0x5d, 0xfb, 0xeb, 0x39, 0x6f, - 0x46, 0x49, 0x2b, 0xd9, 0xc1, 0x11, 0xa5, 0xd9, 0x7d, 0x50, 0x78, 0xc9, 0x94, 0xad, 0x3c, 0x27, - 0x37, 0x08, 0x4e, 0x19, 0x93, 0x60, 0xfc, 0x0f, 0xeb, 0x50, 0xda, 0x9d, 0xbb, 0x23, 0x1c, 0x0c, - 0x76, 0x07, 0x72, 0xe3, 0xb9, 0x3b, 0xa2, 0xb6, 0xc4, 0xdb, 0x6e, 0xbc, 0xa6, 0x34, 0x42, 0xa2, + // 13523 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0xbd, 0x5b, 0x8c, 0x1b, 0x49, + 0x96, 0x18, 0x5a, 0x2c, 0xbe, 0x0f, 0xc9, 0xaa, 0xac, 0x50, 0x49, 0xa2, 0xd4, 0x6a, 0xa9, 0x3a, + 0xa5, 0x56, 0xab, 0xd5, 0xdd, 0xea, 0x6e, 0xa9, 0x9f, 0xb3, 0xb3, 0x3b, 0xc3, 0x22, 0x59, 0x2a, + 0x76, 0xb3, 0xc8, 0x9a, 0x24, 0x4b, 0xea, 0x9e, 0xbd, 0x8b, 0x44, 0x16, 0x33, 0xc9, 0xca, 0xae, + 0x64, 0x26, 0x3b, 0x33, 0xa9, 0xaa, 0x1a, 0x60, 0x71, 0x07, 0xf7, 0x63, 0x1f, 0xc0, 0xfd, 0xbb, + 0xb8, 0xf7, 0x7e, 0x2d, 0xb0, 0x36, 0x60, 0x18, 0x30, 0x6c, 0x18, 0x36, 0x06, 0x58, 0xef, 0x9f, + 0x07, 0xfe, 0x59, 0xc3, 0x0f, 0xf8, 0x01, 0x7b, 0xb1, 0x36, 0xb0, 0xb6, 0xc7, 0xbf, 0x36, 0x6c, + 0xc0, 0x06, 0x0c, 0xd8, 0x1f, 0x36, 0xce, 0x89, 0xc8, 0xcc, 0x48, 0x92, 0x92, 0xba, 0x7b, 0x66, + 0x01, 0xff, 0x54, 0x31, 0xce, 0x23, 0xde, 0x71, 0xe2, 0x9c, 0x13, 0x27, 0x22, 0x01, 0x66, 0x8e, + 0xe1, 0x3e, 0x98, 0xf9, 0x5e, 0xe8, 0xb1, 0x1c, 0xfe, 0xbe, 0xfe, 0xce, 0xc4, 0x0e, 0x4f, 0xe6, + 0xc7, 0x0f, 0x46, 0xde, 0xf4, 0xdd, 0x89, 0x37, 0xf1, 0xde, 0x25, 0xe4, 0xf1, 0x7c, 0x4c, 0x29, + 0x4a, 0xd0, 0x2f, 0xce, 0x74, 0x1d, 0x1c, 0x6f, 0x74, 0x2a, 0x7e, 0x6f, 0x86, 0xf6, 0xd4, 0x0a, + 0x42, 0x63, 0x3a, 0xe3, 0x00, 0xf5, 0x8f, 0x32, 0x90, 0x1b, 0x5e, 0xcc, 0x2c, 0xb6, 0x01, 0xeb, + 0xb6, 0x59, 0xcf, 0xec, 0x64, 0xee, 0xe5, 0xb5, 0x75, 0xdb, 0x64, 0x3b, 0x50, 0x71, 0xbd, 0xb0, + 0x37, 0x77, 0x1c, 0xe3, 0xd8, 0xb1, 0xea, 0xeb, 0x3b, 0x99, 0x7b, 0x25, 0x4d, 0x06, 0xb1, 0x57, + 0xa0, 0x6c, 0xcc, 0x43, 0x4f, 0xb7, 0xdd, 0x91, 0x5f, 0xcf, 0x12, 0xbe, 0x84, 0x80, 0x8e, 0x3b, + 0xf2, 0xd9, 0x36, 0xe4, 0xcf, 0x6c, 0x33, 0x3c, 0xa9, 0xe7, 0x28, 0x47, 0x9e, 0x40, 0x68, 0x30, + 0x32, 0x1c, 0xab, 0x9e, 0xe7, 0x50, 0x4a, 0x20, 0x34, 0xa4, 0x42, 0x0a, 0x3b, 0x99, 0x7b, 0x65, + 0x8d, 0x27, 0xd8, 0x4d, 0x00, 0xcb, 0x9d, 0x4f, 0x9f, 0x19, 0xce, 0xdc, 0x0a, 0xea, 0x45, 0x42, + 0x49, 0x10, 0xf5, 0x07, 0x50, 0x9e, 0x06, 0x93, 0x7d, 0xcb, 0x30, 0x2d, 0x9f, 0x5d, 0x85, 0xe2, + 0x34, 0x98, 0xe8, 0xa1, 0x31, 0x11, 0x4d, 0x28, 0x4c, 0x83, 0xc9, 0xd0, 0x98, 0xb0, 0x6b, 0x50, + 0x22, 0xc4, 0xc5, 0x8c, 0xb7, 0x21, 0xaf, 0x21, 0x21, 0xb6, 0x58, 0xfd, 0xfd, 0x02, 0x14, 0xbb, + 0x76, 0x68, 0xf9, 0x86, 0xc3, 0xae, 0x40, 0xc1, 0x0e, 0xdc, 0xb9, 0xe3, 0x10, 0x7b, 0x49, 0x13, + 0x29, 0x76, 0x05, 0xf2, 0xf6, 0x27, 0xcf, 0x0c, 0x87, 0xf3, 0xee, 0xaf, 0x69, 0x3c, 0xc9, 0xea, + 0x50, 0xb0, 0xdf, 0xff, 0x08, 0x11, 0x59, 0x81, 0x10, 0x69, 0xc2, 0x3c, 0x7a, 0x88, 0x98, 0x5c, + 0x8c, 0xa1, 0x34, 0x61, 0x3e, 0xfa, 0x00, 0x31, 0xd8, 0xfa, 0x2c, 0x61, 0x28, 0x8d, 0xa5, 0xcc, + 0xa9, 0x14, 0xec, 0x80, 0x1a, 0x96, 0x32, 0x8f, 0x4a, 0x99, 0xf3, 0x52, 0x8a, 0x02, 0x21, 0xd2, + 0x84, 0xe1, 0xa5, 0x94, 0x62, 0x4c, 0x5c, 0xca, 0x9c, 0x97, 0x52, 0xde, 0xc9, 0xdc, 0xcb, 0x11, + 0x86, 0x97, 0xb2, 0x0d, 0x39, 0x13, 0xe1, 0xb0, 0x93, 0xb9, 0x97, 0xd9, 0x5f, 0xd3, 0x28, 0x85, + 0xd0, 0x00, 0xa1, 0x15, 0xec, 0x60, 0x84, 0x06, 0x02, 0x7a, 0x8c, 0xd0, 0x2a, 0xf6, 0x06, 0x42, + 0x8f, 0x05, 0x74, 0x8c, 0xd0, 0xda, 0x4e, 0xe6, 0xde, 0x3a, 0x42, 0x31, 0xc5, 0xae, 0x43, 0xd1, + 0x34, 0x42, 0x0b, 0x11, 0x1b, 0xa2, 0xc9, 0x11, 0x00, 0x71, 0x38, 0xe3, 0x10, 0xb7, 0x29, 0x1a, + 0x1d, 0x01, 0x98, 0x0a, 0x15, 0x24, 0x8b, 0xf0, 0x8a, 0xc0, 0xcb, 0x40, 0xf6, 0x21, 0x54, 0x4d, + 0x6b, 0x64, 0x4f, 0x0d, 0x87, 0xb7, 0x69, 0x6b, 0x27, 0x73, 0xaf, 0xf2, 0x70, 0xf3, 0x01, 0xad, + 0x89, 0x18, 0xb3, 0xbf, 0xa6, 0xa5, 0xc8, 0xd8, 0x27, 0x50, 0x13, 0xe9, 0xf7, 0x1f, 0x52, 0xc7, + 0x32, 0xe2, 0x53, 0x52, 0x7c, 0xef, 0x3f, 0xfc, 0x64, 0x7f, 0x4d, 0x4b, 0x13, 0xb2, 0x3b, 0x50, + 0x8d, 0x97, 0x08, 0x32, 0x5e, 0x12, 0xb5, 0x4a, 0x41, 0xb1, 0x59, 0x5f, 0x05, 0x9e, 0x8b, 0x04, + 0xdb, 0xa2, 0xdf, 0x22, 0x00, 0xdb, 0x01, 0x30, 0xad, 0xb1, 0x31, 0x77, 0x42, 0x44, 0x5f, 0x16, + 0x1d, 0x28, 0xc1, 0xd8, 0x4d, 0x28, 0xcf, 0x67, 0xd8, 0xca, 0x27, 0x86, 0x53, 0xbf, 0x22, 0x08, + 0x12, 0x10, 0xe6, 0x8e, 0xf3, 0x1c, 0xb1, 0x57, 0xc5, 0xe8, 0x46, 0x00, 0x1c, 0xde, 0x67, 0xd6, + 0x08, 0x51, 0x75, 0x51, 0xb0, 0x48, 0xe3, 0x2a, 0xb2, 0x83, 0x5d, 0xdb, 0xad, 0x5f, 0xa3, 0x19, + 0xcc, 0x13, 0xec, 0x06, 0x64, 0x03, 0x7f, 0x54, 0xbf, 0x4e, 0xed, 0x07, 0xde, 0xfe, 0xf6, 0xf9, + 0xcc, 0xd7, 0x10, 0xbc, 0x5b, 0x84, 0x3c, 0xad, 0x26, 0xf5, 0x06, 0x94, 0x0e, 0x0d, 0xdf, 0x98, + 0x6a, 0xd6, 0x98, 0x29, 0x90, 0x9d, 0x79, 0x81, 0x58, 0x47, 0xf8, 0x53, 0xed, 0x42, 0xe1, 0x89, + 0xe1, 0x23, 0x8e, 0x41, 0xce, 0x35, 0xa6, 0x16, 0x21, 0xcb, 0x1a, 0xfd, 0xc6, 0xb5, 0x13, 0x5c, + 0x04, 0xa1, 0x35, 0x15, 0x42, 0x42, 0xa4, 0x10, 0x3e, 0x71, 0xbc, 0x63, 0xb1, 0x46, 0x4a, 0x9a, + 0x48, 0xa9, 0xff, 0x57, 0x06, 0x0a, 0x4d, 0xcf, 0xc1, 0xec, 0xae, 0x42, 0xd1, 0xb7, 0x1c, 0x3d, + 0x29, 0xae, 0xe0, 0x5b, 0xce, 0xa1, 0x17, 0x20, 0x62, 0xe4, 0x71, 0x04, 0x5f, 0xb5, 0x85, 0x91, + 0x47, 0x88, 0xa8, 0x02, 0x59, 0xa9, 0x02, 0xd7, 0xa0, 0x14, 0x1e, 0x3b, 0x3a, 0xc1, 0x73, 0x04, + 0x2f, 0x86, 0xc7, 0x4e, 0x0f, 0x51, 0x57, 0xa1, 0x68, 0x1e, 0x73, 0x4c, 0x9e, 0x30, 0x05, 0xf3, + 0x18, 0x11, 0xea, 0xa7, 0x50, 0xd6, 0x8c, 0x33, 0x51, 0x8d, 0xcb, 0x50, 0xc0, 0x0c, 0x84, 0xfc, + 0xcb, 0x69, 0xf9, 0xf0, 0xd8, 0xe9, 0x98, 0x08, 0xc6, 0x4a, 0xd8, 0x26, 0xd5, 0x21, 0xa7, 0xe5, + 0x47, 0x9e, 0xd3, 0x31, 0xd5, 0x21, 0x40, 0xd3, 0xf3, 0xfd, 0xef, 0xdc, 0x84, 0x6d, 0xc8, 0x9b, + 0xd6, 0x2c, 0x3c, 0xe1, 0xa2, 0x43, 0xe3, 0x09, 0xf5, 0x3e, 0x94, 0x70, 0x5c, 0xba, 0x76, 0x10, + 0xb2, 0x9b, 0x90, 0x73, 0xec, 0x20, 0xac, 0x67, 0x76, 0xb2, 0x0b, 0xa3, 0x46, 0x70, 0x75, 0x07, + 0x4a, 0x07, 0xc6, 0xf9, 0x13, 0x1c, 0x39, 0xcc, 0x8d, 0x86, 0x50, 0x0c, 0x89, 0x18, 0xcf, 0x2a, + 0xc0, 0xd0, 0xf0, 0x27, 0x56, 0x48, 0x92, 0xee, 0xbf, 0x64, 0xa0, 0x32, 0x98, 0x1f, 0x7f, 0x3d, + 0xb7, 0xfc, 0x0b, 0xac, 0xf3, 0x3d, 0xc8, 0x86, 0x17, 0x33, 0xe2, 0xd8, 0x78, 0x78, 0x85, 0x67, + 0x2f, 0xe1, 0x1f, 0x20, 0x93, 0x86, 0x24, 0xd8, 0x08, 0xd7, 0x33, 0xad, 0xa8, 0x0f, 0xf2, 0x5a, + 0x01, 0x93, 0x1d, 0x13, 0xb7, 0x0b, 0x6f, 0x26, 0x46, 0x61, 0xdd, 0x9b, 0xb1, 0x1d, 0xc8, 0x8f, + 0x4e, 0x6c, 0xc7, 0xa4, 0x01, 0x48, 0xd7, 0x99, 0x23, 0x70, 0x94, 0x7c, 0xef, 0x4c, 0x0f, 0xec, + 0x9f, 0x44, 0xe2, 0xbf, 0xe8, 0x7b, 0x67, 0x03, 0xfb, 0x27, 0x96, 0x3a, 0x14, 0x7b, 0x10, 0x40, + 0x61, 0xd0, 0x6c, 0x74, 0x1b, 0x9a, 0xb2, 0x86, 0xbf, 0xdb, 0x5f, 0x74, 0x06, 0xc3, 0x81, 0x92, + 0x61, 0x1b, 0x00, 0xbd, 0xfe, 0x50, 0x17, 0xe9, 0x75, 0x56, 0x80, 0xf5, 0x4e, 0x4f, 0xc9, 0x22, + 0x0d, 0xc2, 0x3b, 0x3d, 0x25, 0xc7, 0x8a, 0x90, 0x6d, 0xf4, 0xbe, 0x54, 0xf2, 0xf4, 0xa3, 0xdb, + 0x55, 0x0a, 0xea, 0x7f, 0x5f, 0x87, 0x72, 0xff, 0xf8, 0x2b, 0x6b, 0x14, 0x62, 0x9b, 0x71, 0x96, + 0x5a, 0xfe, 0x33, 0xcb, 0xa7, 0x66, 0x67, 0x35, 0x91, 0xc2, 0x86, 0x98, 0xc7, 0xd4, 0xb8, 0xac, + 0xb6, 0x6e, 0x1e, 0x13, 0xdd, 0xe8, 0xc4, 0x9a, 0x1a, 0xd4, 0x38, 0xa4, 0xa3, 0x14, 0xae, 0x0a, + 0xef, 0xf8, 0x2b, 0x6a, 0x5e, 0x56, 0xc3, 0x9f, 0xec, 0x16, 0x54, 0x78, 0x1e, 0xf2, 0xfc, 0x02, + 0x0e, 0x5a, 0x9c, 0x7c, 0x05, 0x79, 0xf2, 0x11, 0x27, 0xe5, 0xca, 0x91, 0x62, 0x6f, 0xe3, 0xa0, + 0x9e, 0x98, 0xd1, 0xde, 0xf1, 0x57, 0x1c, 0x5b, 0xe2, 0x33, 0xda, 0x3b, 0xfe, 0x8a, 0x50, 0x6f, + 0xc1, 0x56, 0x30, 0x3f, 0x0e, 0x46, 0xbe, 0x3d, 0x0b, 0x6d, 0xcf, 0xe5, 0x34, 0x65, 0xa2, 0x51, + 0x64, 0x04, 0x11, 0xdf, 0x83, 0xd2, 0x6c, 0x7e, 0xac, 0xdb, 0xee, 0xd8, 0x23, 0xb1, 0x5f, 0x79, + 0x58, 0xe3, 0x03, 0x73, 0x38, 0x3f, 0xee, 0xb8, 0x63, 0x4f, 0x2b, 0xce, 0xf8, 0x0f, 0xa6, 0x42, + 0xcd, 0xf5, 0x42, 0x1d, 0x55, 0x05, 0x7d, 0x6a, 0x85, 0x06, 0xed, 0x07, 0x7c, 0xc3, 0xef, 0x7a, + 0xa3, 0xd3, 0x03, 0x2b, 0x34, 0xd8, 0x7d, 0x28, 0x05, 0xae, 0x31, 0x0b, 0x4e, 0xbc, 0x90, 0x36, + 0x86, 0xca, 0xc3, 0x0d, 0x31, 0x77, 0x04, 0x54, 0x8b, 0xf1, 0xea, 0x5d, 0x28, 0x8a, 0x32, 0x50, + 0x4f, 0x08, 0x2d, 0xd7, 0x70, 0x43, 0x3d, 0x56, 0x30, 0x4a, 0x1c, 0xd0, 0x31, 0xd5, 0x9f, 0x65, + 0x40, 0x19, 0x48, 0xd5, 0xa6, 0x82, 0x56, 0x49, 0x99, 0x57, 0x01, 0x8c, 0xd1, 0xc8, 0x9b, 0xf3, + 0x6c, 0xf8, 0x64, 0x2c, 0x0b, 0x48, 0xc7, 0x94, 0xfb, 0x3a, 0x9b, 0xea, 0xeb, 0xd7, 0xa0, 0x1a, + 0xf1, 0x49, 0x02, 0xa2, 0x22, 0x60, 0x51, 0x6f, 0x07, 0xf3, 0x94, 0x94, 0x28, 0x06, 0x73, 0xce, + 0x7d, 0x05, 0x0a, 0xa4, 0x8d, 0x04, 0xd1, 0x08, 0xf2, 0x94, 0xfa, 0xa7, 0x19, 0xa8, 0x75, 0x5c, + 0xd3, 0x3a, 0x1f, 0x8c, 0x0c, 0x37, 0xea, 0x40, 0x3b, 0xd0, 0x6d, 0x84, 0xe9, 0xc1, 0xc8, 0x70, + 0x85, 0x22, 0x51, 0xb1, 0x83, 0x98, 0x0e, 0xdb, 0xc0, 0x09, 0xa8, 0xa8, 0x75, 0xca, 0xb1, 0x4c, + 0x10, 0x2a, 0xec, 0x2e, 0x6c, 0x1e, 0x5b, 0x8e, 0xe7, 0x4e, 0xf4, 0xd0, 0xd3, 0xb9, 0x46, 0xc4, + 0xdb, 0x52, 0xe3, 0xe0, 0xa1, 0x37, 0x24, 0xcd, 0x68, 0x1b, 0xf2, 0x33, 0xc3, 0x0f, 0x83, 0x7a, + 0x6e, 0x27, 0x8b, 0x4b, 0x9e, 0x12, 0xd8, 0xcd, 0x76, 0xa0, 0xcf, 0x5d, 0xfb, 0xeb, 0x39, 0x6f, + 0x46, 0x49, 0x2b, 0xd9, 0xc1, 0x11, 0xa5, 0xd9, 0x3d, 0x50, 0x78, 0xc9, 0x94, 0xad, 0x3c, 0x27, + 0x37, 0x08, 0x4e, 0x19, 0x93, 0x60, 0xfc, 0x0f, 0xeb, 0x50, 0xda, 0x9b, 0xbb, 0x23, 0x1c, 0x0c, + 0x76, 0x1b, 0x72, 0xe3, 0xb9, 0x3b, 0xa2, 0xb6, 0xc4, 0xdb, 0x6e, 0xbc, 0xa6, 0x34, 0x42, 0xa2, 0xb4, 0x32, 0xfc, 0x09, 0x4a, 0xb9, 0x25, 0x69, 0x85, 0x70, 0x1a, 0xb9, 0xc9, 0x44, 0x1f, 0x79, 0xee, 0xd8, 0x9e, 0x50, 0x8b, 0xaa, 0x5a, 0xd9, 0x98, 0x4c, 0x9a, 0x04, 0x60, 0x0d, 0xd8, 0x4c, - 0xd0, 0x5c, 0x51, 0xcb, 0x91, 0x60, 0xba, 0xce, 0x73, 0x6a, 0x4c, 0x26, 0xbe, 0x35, 0x31, 0x42, - 0x8b, 0xd3, 0x93, 0x6c, 0xaa, 0xc5, 0xec, 0x24, 0xdf, 0xfe, 0x6e, 0x86, 0xd7, 0x79, 0xd7, 0x31, + 0xd0, 0x5c, 0x51, 0xcb, 0x91, 0x60, 0xba, 0xc6, 0x73, 0x6a, 0x4c, 0x26, 0xbe, 0x35, 0x31, 0x42, + 0x8b, 0xd3, 0x93, 0x6c, 0xaa, 0xc5, 0xec, 0x24, 0xdf, 0xfe, 0x4e, 0x86, 0xd7, 0x79, 0xcf, 0x31, 0x26, 0xac, 0x04, 0xb9, 0x5e, 0xbf, 0xd7, 0x56, 0xd6, 0x58, 0x15, 0x4a, 0x9d, 0xde, 0xb0, 0xad, - 0xf5, 0x1a, 0x5d, 0x25, 0x43, 0xc2, 0x65, 0xd8, 0xd8, 0xe9, 0xb6, 0x95, 0x75, 0xc4, 0x3c, 0xe9, + 0xf5, 0x1a, 0x5d, 0x25, 0x43, 0xc2, 0x65, 0xd8, 0xd8, 0xed, 0xb6, 0x95, 0x75, 0xc4, 0x3c, 0xe9, 0x77, 0x1b, 0xc3, 0x4e, 0xb7, 0xad, 0xe4, 0x38, 0x46, 0xeb, 0x34, 0x87, 0x4a, 0x89, 0x29, 0x50, 0x3d, 0xd4, 0xfa, 0xad, 0xa3, 0x66, 0x5b, 0xef, 0x1d, 0x75, 0xbb, 0x8a, 0xc2, 0x2e, 0xc1, 0x66, - 0x0c, 0xe9, 0x73, 0xe0, 0x36, 0xb2, 0x3c, 0x69, 0x68, 0x0d, 0x6d, 0x4f, 0xf9, 0x21, 0x2b, 0x41, - 0xb6, 0xb1, 0xb7, 0xa7, 0xfc, 0x14, 0xe5, 0x54, 0xf9, 0x69, 0xa7, 0xa7, 0x3f, 0x69, 0x74, 0x8f, - 0xda, 0xca, 0x4f, 0xd7, 0xa3, 0x74, 0x5f, 0x6b, 0xb5, 0x35, 0xe5, 0xa7, 0x39, 0xb6, 0x05, 0xd5, - 0x1f, 0xf7, 0x7b, 0xed, 0x83, 0xc6, 0xe1, 0x21, 0x55, 0xe4, 0xa7, 0x25, 0xf5, 0x3f, 0xe6, 0x20, - 0x87, 0x7d, 0xc5, 0xd4, 0x44, 0x26, 0xc7, 0x9d, 0x88, 0x8d, 0xdb, 0xc9, 0xfd, 0xc9, 0x9f, 0xdf, - 0x5e, 0xe3, 0xd2, 0xf8, 0x35, 0xc8, 0x3a, 0x76, 0x48, 0x13, 0x27, 0x5e, 0xc9, 0x42, 0x83, 0xdd, - 0x5f, 0xd3, 0x10, 0xc7, 0x6e, 0x41, 0x86, 0x8b, 0xe5, 0x78, 0x71, 0x46, 0xfb, 0xfa, 0xfe, 0x9a, - 0x96, 0x99, 0xb1, 0x9b, 0x90, 0x79, 0x26, 0x64, 0x74, 0x95, 0xe3, 0xf9, 0xce, 0x8e, 0xd8, 0x67, - 0x6c, 0x1b, 0xb2, 0x23, 0x8f, 0xeb, 0xa7, 0x31, 0x9e, 0xef, 0x73, 0x98, 0xff, 0xc8, 0x73, 0xd8, - 0x1d, 0xc8, 0xfa, 0xc6, 0x19, 0xcd, 0x9d, 0x78, 0x42, 0xc4, 0x1b, 0x29, 0x12, 0xf9, 0xc6, 0x19, - 0x56, 0x62, 0x4c, 0x52, 0x2d, 0xae, 0x44, 0x34, 0xa3, 0xb0, 0x98, 0x31, 0xdb, 0x86, 0xcc, 0x19, - 0xc9, 0xb5, 0x58, 0x25, 0x7b, 0x6a, 0xbb, 0xa6, 0x77, 0x36, 0x98, 0x59, 0x23, 0xa4, 0x38, 0x63, - 0xaf, 0x43, 0x36, 0x98, 0x1f, 0x93, 0x5c, 0xab, 0x3c, 0xda, 0x5a, 0xda, 0xa1, 0xb0, 0xa0, 0x60, - 0x7e, 0xcc, 0xee, 0x41, 0x6e, 0xe4, 0xf9, 0xbe, 0x90, 0x6d, 0x4a, 0x54, 0xe1, 0x68, 0x73, 0x46, - 0x15, 0x15, 0xf1, 0x58, 0x60, 0x48, 0x12, 0x2d, 0x26, 0x4a, 0x76, 0x47, 0x2c, 0x30, 0x64, 0x77, - 0xc5, 0x96, 0x9b, 0x92, 0x6b, 0xd1, 0x86, 0x8c, 0xf9, 0x20, 0x16, 0x07, 0x69, 0x6a, 0x9c, 0x93, - 0xfe, 0x1b, 0x13, 0x45, 0x3b, 0x31, 0xd6, 0x69, 0x6a, 0x9c, 0xb3, 0xbb, 0x90, 0x7d, 0x66, 0x8d, - 0x48, 0x15, 0x8e, 0x4b, 0x13, 0x83, 0xf4, 0x84, 0x9a, 0x87, 0x68, 0x5a, 0x59, 0x9e, 0x63, 0x92, - 0x56, 0x1c, 0x8f, 0xe5, 0xae, 0xe7, 0x98, 0x4f, 0x68, 0x2c, 0x09, 0x89, 0x0a, 0x88, 0x31, 0x3f, - 0x47, 0x79, 0xa7, 0x70, 0x55, 0xc1, 0x98, 0x9f, 0x77, 0x4c, 0xdc, 0x8a, 0x5c, 0xf3, 0x19, 0xe9, - 0xc2, 0x19, 0x0d, 0x7f, 0xa2, 0xb1, 0x16, 0x58, 0x8e, 0x35, 0x0a, 0xed, 0x67, 0x76, 0x78, 0x41, - 0xda, 0x6e, 0x46, 0x93, 0x41, 0x3b, 0x05, 0xc8, 0x59, 0xe7, 0x33, 0x5f, 0xdd, 0x87, 0xa2, 0x28, - 0x65, 0xc9, 0xe2, 0xbb, 0x0e, 0x25, 0x3b, 0xc0, 0x75, 0x18, 0x84, 0x42, 0x93, 0x2b, 0xda, 0x41, - 0x13, 0x93, 0x28, 0x90, 0x4d, 0x23, 0x34, 0xc4, 0xe2, 0xa5, 0xdf, 0xea, 0x23, 0x80, 0xa4, 0x59, - 0x58, 0x27, 0xc7, 0x72, 0x23, 0xa5, 0xd1, 0xb1, 0xdc, 0x98, 0x67, 0x5d, 0xe2, 0xb9, 0x0e, 0xe5, - 0x58, 0x4f, 0x67, 0x55, 0xc8, 0x18, 0x62, 0x33, 0xce, 0x18, 0xea, 0x7d, 0x54, 0x9b, 0x23, 0x4d, - 0x3c, 0x8d, 0xc3, 0x54, 0xb4, 0x45, 0x67, 0x8e, 0xd5, 0xef, 0x43, 0x55, 0xb3, 0x82, 0xb9, 0x13, - 0x36, 0x3d, 0xa7, 0x65, 0x8d, 0xd9, 0xdb, 0x00, 0x71, 0x3a, 0x10, 0x3a, 0x53, 0x32, 0x77, 0x5b, - 0xd6, 0x58, 0x93, 0xf0, 0xea, 0xbf, 0xcb, 0x91, 0xf6, 0xd9, 0xe2, 0x6a, 0x9f, 0xd0, 0xef, 0x32, - 0x92, 0x7e, 0x17, 0xef, 0x3e, 0xeb, 0x69, 0x1d, 0xf7, 0xc4, 0x36, 0x4d, 0xcb, 0x8d, 0x74, 0x59, - 0x9e, 0xc2, 0xc1, 0x36, 0x9c, 0x89, 0x10, 0x58, 0x2c, 0x2a, 0x74, 0x3a, 0xf3, 0xad, 0x20, 0xe0, - 0x5a, 0x94, 0xe1, 0x4c, 0xa2, 0xb5, 0x9d, 0x7f, 0xd1, 0xda, 0xbe, 0x0e, 0x25, 0xdc, 0x80, 0xc9, - 0x06, 0x2d, 0xf0, 0xde, 0x17, 0xc6, 0x36, 0x7b, 0x03, 0x8a, 0xc2, 0x7a, 0x10, 0x8b, 0x4a, 0x4c, - 0x97, 0x16, 0x07, 0x6a, 0x11, 0x96, 0xd5, 0x51, 0xe5, 0x9c, 0x4e, 0x2d, 0x37, 0x8c, 0xb4, 0x06, - 0x91, 0x64, 0x6f, 0x41, 0xd9, 0x73, 0x75, 0x6e, 0x62, 0x88, 0x55, 0x25, 0xa6, 0x6f, 0xdf, 0x3d, - 0x22, 0xa8, 0x56, 0xf2, 0xc4, 0x2f, 0xac, 0x8a, 0xe3, 0x9d, 0xe9, 0x23, 0xc3, 0x37, 0x69, 0x65, - 0x95, 0xb4, 0xa2, 0xe3, 0x9d, 0x35, 0x0d, 0xdf, 0xe4, 0x5a, 0xd4, 0xd7, 0xee, 0x7c, 0x4a, 0xab, - 0xa9, 0xa6, 0x89, 0x14, 0xbb, 0x09, 0xe5, 0x91, 0x33, 0x0f, 0x42, 0xcb, 0xdf, 0xb9, 0xe0, 0x46, - 0xa3, 0x96, 0x00, 0xb0, 0x5e, 0x33, 0xdf, 0x9e, 0x1a, 0xfe, 0x05, 0x2d, 0x9d, 0x92, 0x16, 0x25, - 0x69, 0x2b, 0x3b, 0xb5, 0xcd, 0x73, 0x6e, 0x39, 0x6a, 0x3c, 0x81, 0xf4, 0x27, 0x64, 0xd7, 0x07, - 0xb4, 0x3e, 0x4a, 0x5a, 0x94, 0xa4, 0x71, 0xa0, 0x9f, 0xb4, 0x22, 0xca, 0x9a, 0x48, 0xa5, 0x4c, - 0x80, 0xad, 0xe7, 0x9a, 0x00, 0x6c, 0x51, 0x0b, 0xf3, 0x7c, 0x7b, 0x62, 0x0b, 0x1d, 0xea, 0x12, - 0xd7, 0xc2, 0x38, 0x88, 0x08, 0x3e, 0x86, 0xda, 0xc4, 0x72, 0x2d, 0xdf, 0x08, 0x2d, 0x53, 0x47, - 0xb9, 0x78, 0x99, 0x3a, 0x4e, 0x0c, 0xf3, 0x5e, 0x84, 0x42, 0x59, 0x53, 0x9d, 0x48, 0x29, 0xf5, - 0x6b, 0x28, 0x8a, 0xb1, 0xc1, 0xcd, 0x11, 0xd7, 0x5d, 0x5a, 0xae, 0xf3, 0xcd, 0x11, 0xe1, 0xec, - 0x0e, 0xd4, 0x44, 0x25, 0x82, 0xd0, 0xb7, 0xdd, 0x89, 0x98, 0x75, 0x55, 0x0e, 0x1c, 0x10, 0x0c, - 0x75, 0x18, 0x9c, 0x17, 0xba, 0x71, 0x6c, 0x3b, 0xb8, 0xbe, 0xb3, 0x42, 0x37, 0x9b, 0x3b, 0x4e, - 0x83, 0x83, 0xd4, 0x3e, 0x94, 0xa2, 0x91, 0xfc, 0x95, 0x94, 0xa9, 0xce, 0xa0, 0x2a, 0xb7, 0xf0, - 0x57, 0xd3, 0x10, 0xae, 0xa3, 0x04, 0xa1, 0xe7, 0x5b, 0x66, 0xe4, 0x32, 0xb2, 0x83, 0x01, 0xa5, - 0xd5, 0xdf, 0xc9, 0x40, 0x85, 0x74, 0xa5, 0x3e, 0x69, 0x82, 0xec, 0x6d, 0x60, 0x23, 0xdf, 0x32, - 0x42, 0x4b, 0xb7, 0xce, 0x43, 0xdf, 0x10, 0x1a, 0x11, 0x57, 0xab, 0x14, 0x8e, 0x69, 0x23, 0x82, - 0x2b, 0x45, 0xb7, 0xa1, 0x32, 0x33, 0xfc, 0x20, 0xd2, 0xc6, 0x79, 0xe9, 0xc0, 0x41, 0x42, 0x17, - 0x56, 0xdc, 0x89, 0x6f, 0x4c, 0xf5, 0xd0, 0x3b, 0xb5, 0x5c, 0x6e, 0x87, 0x70, 0x0b, 0x6c, 0x83, - 0xe0, 0x43, 0x04, 0x93, 0x39, 0xf2, 0xaf, 0x32, 0x50, 0x3b, 0xe4, 0x13, 0xf4, 0x73, 0xeb, 0xa2, - 0xc5, 0xcd, 0xde, 0x51, 0x24, 0x5c, 0x72, 0x1a, 0xfd, 0x66, 0xb7, 0xa0, 0x32, 0x3b, 0xb5, 0x2e, - 0xf4, 0x94, 0x89, 0x58, 0x46, 0x50, 0x93, 0xc4, 0xc8, 0x9b, 0x50, 0xf0, 0xa8, 0x21, 0x62, 0x3b, - 0x16, 0xbb, 0x98, 0xd4, 0x42, 0x4d, 0x10, 0xa0, 0xee, 0x18, 0x67, 0x25, 0x2b, 0xa9, 0x22, 0x33, - 0xaa, 0xfe, 0x65, 0xc8, 0x23, 0x2a, 0xa8, 0xe7, 0xb9, 0xd2, 0x47, 0x09, 0xf6, 0x1e, 0xd4, 0x46, - 0xde, 0x74, 0xa6, 0x47, 0xec, 0x62, 0x63, 0x4e, 0x8b, 0xbf, 0x0a, 0x92, 0x1c, 0xf2, 0xbc, 0xd4, - 0x9f, 0x67, 0xa1, 0x44, 0x75, 0x10, 0x12, 0xd0, 0x36, 0xcf, 0x23, 0x09, 0x58, 0xd6, 0xf2, 0xb6, - 0x89, 0x1b, 0xcc, 0x4b, 0xf4, 0xd4, 0x58, 0xff, 0xcc, 0xca, 0xfa, 0xe7, 0x55, 0x28, 0x08, 0xe5, - 0x33, 0xc7, 0x45, 0xe4, 0xfc, 0xf9, 0xaa, 0x67, 0x7e, 0x95, 0xea, 0x89, 0x43, 0xc8, 0x69, 0xac, - 0x73, 0xdc, 0x8a, 0xb9, 0x14, 0x04, 0x02, 0xb5, 0x11, 0x22, 0xcb, 0xb7, 0x62, 0x5a, 0xbe, 0xd5, - 0xa1, 0xf8, 0xcc, 0x0e, 0x6c, 0x9c, 0x20, 0x25, 0x2e, 0x31, 0x44, 0x52, 0x1a, 0x86, 0xf2, 0xcb, - 0x86, 0x21, 0x6e, 0xb6, 0xe1, 0x4c, 0xb8, 0xbd, 0x14, 0x35, 0xbb, 0xe1, 0x4c, 0x3c, 0xf6, 0x3e, - 0x5c, 0x49, 0xd0, 0xa2, 0x35, 0xa4, 0xae, 0x92, 0xeb, 0x4c, 0x63, 0x31, 0x25, 0xb5, 0x88, 0x0c, - 0xda, 0x07, 0xb0, 0x25, 0xb1, 0xcc, 0x50, 0x13, 0x0b, 0x48, 0x3c, 0x96, 0xb5, 0xcd, 0x98, 0x9c, - 0x14, 0xb4, 0x80, 0xbd, 0x89, 0xfd, 0x34, 0x72, 0xe6, 0x26, 0x17, 0x36, 0xf3, 0xa9, 0x1b, 0xd4, - 0x6b, 0xd4, 0xc1, 0x9b, 0x11, 0xbc, 0xc9, 0xc1, 0xea, 0x3f, 0x58, 0x87, 0xda, 0xae, 0xe7, 0x5b, - 0xf6, 0xc4, 0x4d, 0x26, 0xe8, 0x92, 0xc5, 0x14, 0x4d, 0xda, 0x75, 0x69, 0xd2, 0xde, 0x86, 0xca, - 0x98, 0x33, 0xea, 0xe1, 0x31, 0x77, 0xcc, 0xe4, 0x34, 0x10, 0xa0, 0xe1, 0xb1, 0x83, 0xa2, 0x26, - 0x22, 0x20, 0xe6, 0x1c, 0x31, 0x47, 0x4c, 0xb8, 0x83, 0xb2, 0xef, 0xd1, 0x5e, 0x62, 0x5a, 0x8e, - 0x15, 0xf2, 0x91, 0xdc, 0x78, 0xf4, 0x6a, 0xa4, 0xbf, 0x48, 0x75, 0x7a, 0xa8, 0x59, 0xe3, 0x06, - 0x29, 0x7d, 0xb8, 0xb5, 0xb4, 0x88, 0x5c, 0xf0, 0x8a, 0x7d, 0xa8, 0xf0, 0x0d, 0x79, 0xb9, 0x58, - 0x53, 0x87, 0x50, 0x8e, 0xc1, 0xa8, 0xc1, 0x6b, 0x6d, 0xa1, 0xb5, 0xaf, 0xb1, 0x0a, 0x14, 0x9b, - 0x8d, 0x41, 0xb3, 0xd1, 0x6a, 0x2b, 0x19, 0x44, 0x0d, 0xda, 0x43, 0xae, 0xa9, 0xaf, 0xb3, 0x4d, - 0xa8, 0x60, 0xaa, 0xd5, 0xde, 0x6d, 0x1c, 0x75, 0x87, 0x4a, 0x96, 0xd5, 0xa0, 0xdc, 0xeb, 0xeb, - 0x8d, 0xe6, 0xb0, 0xd3, 0xef, 0x29, 0x39, 0xf5, 0x87, 0x50, 0x6a, 0x9e, 0x58, 0xa3, 0xd3, 0xe7, - 0xf5, 0x22, 0x39, 0x36, 0xac, 0xd1, 0xa9, 0xd0, 0xba, 0x17, 0x1c, 0x1b, 0xd6, 0xe8, 0x54, 0x6d, - 0x43, 0xf9, 0xd0, 0xf0, 0x43, 0x9b, 0xea, 0xf5, 0x09, 0xd4, 0xe2, 0x44, 0xcb, 0x1a, 0x47, 0xfa, - 0x08, 0x8b, 0x75, 0xf1, 0x18, 0xa5, 0xa5, 0x09, 0xd5, 0xb7, 0xa1, 0x2a, 0x03, 0xd8, 0x4d, 0xc8, - 0x9a, 0xd6, 0x78, 0x85, 0xbc, 0x45, 0xb0, 0xfa, 0x04, 0xaa, 0xcd, 0x68, 0x7f, 0x7d, 0x5e, 0xd5, - 0x1f, 0xc1, 0x06, 0x09, 0x87, 0xd1, 0x71, 0x24, 0x1d, 0xd6, 0x57, 0x48, 0x87, 0x2a, 0xd2, 0x34, - 0x8f, 0x85, 0x78, 0xf8, 0x10, 0x2a, 0x87, 0xbe, 0x37, 0xb3, 0xfc, 0x90, 0xb2, 0x55, 0x20, 0x7b, - 0x6a, 0x5d, 0x88, 0x5c, 0xf1, 0x67, 0xe2, 0x6f, 0x5a, 0x97, 0xfd, 0x4d, 0x8f, 0xa0, 0x14, 0xb1, - 0x7d, 0x63, 0x9e, 0x1f, 0xa0, 0x94, 0x25, 0x1e, 0xdb, 0x0a, 0xb0, 0xb0, 0x87, 0x00, 0xb3, 0x18, - 0x20, 0x3a, 0x2e, 0x32, 0x62, 0x44, 0xe6, 0x9a, 0x44, 0xa1, 0xbe, 0x0a, 0xc5, 0x27, 0xb6, 0x75, - 0x26, 0x9a, 0xff, 0xcc, 0xb6, 0xce, 0xa2, 0xe6, 0xe3, 0x6f, 0xf5, 0xbf, 0x95, 0xa1, 0x44, 0x4b, - 0xb1, 0xf5, 0x7c, 0x17, 0xdf, 0xb7, 0xd1, 0xf5, 0xb6, 0xc5, 0x7a, 0xca, 0xad, 0xd0, 0x30, 0xf9, - 0xea, 0x7a, 0x15, 0x40, 0x12, 0x0b, 0x5c, 0xc8, 0x95, 0xc3, 0x58, 0x1a, 0xa0, 0x92, 0x44, 0xdb, - 0x56, 0xf0, 0xb5, 0x23, 0xac, 0xef, 0x04, 0xc0, 0x1e, 0x72, 0x15, 0x86, 0xec, 0x6d, 0xae, 0xe6, - 0x5d, 0x8a, 0x4c, 0x95, 0x63, 0xc7, 0x8a, 0x0c, 0x28, 0xd2, 0x6b, 0x30, 0x41, 0x22, 0xcf, 0xf2, - 0x03, 0x94, 0x6c, 0x74, 0x06, 0xa0, 0x45, 0x49, 0xf6, 0x06, 0xe4, 0x70, 0x3f, 0x10, 0x06, 0xcf, - 0xa5, 0xa8, 0x07, 0xa5, 0x0d, 0x4d, 0x23, 0x02, 0x76, 0x1f, 0x8a, 0x24, 0x85, 0x2c, 0x14, 0x4a, - 0x52, 0x6f, 0x47, 0xfb, 0x83, 0x16, 0xa1, 0xd9, 0x9b, 0x90, 0x1f, 0x9f, 0x5a, 0x17, 0x5c, 0x22, - 0xc5, 0x79, 0xa6, 0xd6, 0xac, 0xc6, 0x29, 0xd8, 0x5d, 0xd8, 0xf0, 0xad, 0xb1, 0x4e, 0x4e, 0x3f, - 0x14, 0x32, 0x41, 0x7d, 0x83, 0x64, 0x48, 0xd5, 0xb7, 0xc6, 0x4d, 0x04, 0x0e, 0x8f, 0x9d, 0x80, - 0xdd, 0x83, 0x02, 0xad, 0x1e, 0xd4, 0xf0, 0xa4, 0x92, 0xa3, 0xa5, 0xa8, 0x09, 0x2c, 0x7b, 0x1f, - 0x40, 0xe8, 0x91, 0xfa, 0xf1, 0x05, 0x39, 0xcb, 0xe3, 0xc5, 0x24, 0xcf, 0x7f, 0x59, 0xdb, 0x7c, - 0x03, 0xf2, 0x38, 0x49, 0x82, 0xfa, 0x35, 0xca, 0x79, 0x2b, 0x3d, 0x83, 0xa8, 0xa6, 0x84, 0x67, - 0xf7, 0xa1, 0x84, 0x13, 0x45, 0xc7, 0xe1, 0xa8, 0xcb, 0x8a, 0xb5, 0x98, 0x55, 0xb8, 0x89, 0x58, - 0x67, 0x83, 0xaf, 0x1d, 0xf6, 0x00, 0x72, 0x26, 0x2e, 0xe6, 0xeb, 0x94, 0xe3, 0x55, 0x69, 0x5c, - 0x50, 0x58, 0xb5, 0xac, 0x31, 0xe9, 0xfa, 0x44, 0xc3, 0xf6, 0x61, 0x03, 0xa7, 0xd1, 0x23, 0xd2, - 0x0b, 0xb0, 0xfb, 0xea, 0x37, 0x88, 0xeb, 0xb5, 0x05, 0xae, 0x9e, 0x20, 0xa2, 0xce, 0x6e, 0xbb, - 0xa1, 0x7f, 0xa1, 0xd5, 0x5c, 0x19, 0xc6, 0x6e, 0xa0, 0x41, 0xd6, 0xf5, 0x46, 0xa7, 0x96, 0x59, - 0x7f, 0x25, 0x52, 0x96, 0x78, 0x9a, 0x7d, 0x0a, 0x35, 0x9a, 0x58, 0x98, 0xc4, 0xc2, 0xeb, 0x37, - 0x49, 0x98, 0xca, 0x53, 0x26, 0x42, 0x69, 0x69, 0x4a, 0x14, 0xf1, 0x76, 0xa0, 0x87, 0xd6, 0x74, - 0xe6, 0xf9, 0xa8, 0x92, 0xbf, 0x1a, 0x39, 0xaa, 0x86, 0x11, 0x08, 0xf7, 0xec, 0xf8, 0x68, 0x4f, - 0xf7, 0xc6, 0xe3, 0xc0, 0x0a, 0xeb, 0xb7, 0x68, 0xdd, 0x6c, 0x44, 0x27, 0x7c, 0x7d, 0x82, 0xd2, - 0x9e, 0x19, 0xe8, 0xe6, 0x85, 0x6b, 0x4c, 0xed, 0x51, 0xfd, 0x36, 0xd7, 0xfc, 0xed, 0xa0, 0xc5, - 0x01, 0xb2, 0xf2, 0xbd, 0x9d, 0x52, 0xbe, 0x2f, 0x41, 0xde, 0x3c, 0xc6, 0xe5, 0xf8, 0x1a, 0x65, - 0x9b, 0x33, 0x8f, 0x3b, 0x26, 0x7b, 0x07, 0xca, 0xb3, 0x48, 0x04, 0xd6, 0x55, 0xd9, 0xc5, 0x10, - 0x4b, 0x46, 0x2d, 0xa1, 0x40, 0xab, 0x77, 0xd7, 0x32, 0xc2, 0xb9, 0x6f, 0xed, 0x3a, 0xc6, 0xa4, - 0x7e, 0x87, 0x72, 0x92, 0x41, 0x58, 0x3b, 0xc7, 0x9b, 0xd8, 0x23, 0x83, 0x56, 0xfe, 0x5d, 0xae, - 0xa2, 0x09, 0x48, 0xc7, 0x4c, 0x74, 0x56, 0x43, 0xe8, 0x5d, 0xaf, 0xcb, 0x3a, 0xab, 0xe1, 0x44, - 0x5e, 0xb9, 0xa4, 0x2f, 0xac, 0x99, 0x37, 0x3a, 0xa9, 0xdf, 0xa3, 0xf5, 0x56, 0x8b, 0xba, 0xa2, - 0x8d, 0xc0, 0x1b, 0x7b, 0xa4, 0xf4, 0x53, 0x0f, 0x7f, 0xb8, 0x20, 0xc8, 0x52, 0xcb, 0x50, 0x92, - 0x78, 0xfb, 0x6b, 0xb2, 0x3c, 0xdb, 0xc9, 0x93, 0xc4, 0xbf, 0xf1, 0x43, 0x60, 0xcb, 0x73, 0xe3, - 0x65, 0x52, 0x35, 0x2f, 0xa4, 0xea, 0xf7, 0xd6, 0x3f, 0xc9, 0xa8, 0x4f, 0xa0, 0x96, 0x12, 0x1a, - 0x2b, 0x77, 0x07, 0xae, 0xc5, 0x19, 0x53, 0x61, 0xa0, 0xf3, 0x44, 0xa4, 0xa1, 0xdb, 0xee, 0x44, - 0x78, 0x1f, 0xb9, 0x86, 0x4e, 0x69, 0xf5, 0x9f, 0x64, 0xa1, 0xba, 0x6f, 0x04, 0x27, 0x07, 0xc6, - 0x6c, 0x10, 0x1a, 0x61, 0x80, 0x53, 0xe9, 0xc4, 0x08, 0x4e, 0xa6, 0xc6, 0x8c, 0xeb, 0xd3, 0x19, - 0xee, 0x78, 0x10, 0x30, 0x54, 0xa6, 0x71, 0x12, 0x63, 0xb2, 0xef, 0x1e, 0x7e, 0x2e, 0xbc, 0x0a, - 0x71, 0x1a, 0x45, 0x58, 0x70, 0x32, 0x1f, 0x8f, 0xe3, 0xa2, 0xa2, 0x24, 0xbb, 0x0b, 0x35, 0xf1, - 0x93, 0x94, 0xe9, 0x73, 0x71, 0x8c, 0x9c, 0x06, 0xb2, 0xc7, 0x50, 0x11, 0x80, 0x61, 0x24, 0x70, - 0x37, 0x62, 0x6f, 0x51, 0x82, 0xd0, 0x64, 0x2a, 0xf6, 0x23, 0xb8, 0x22, 0x25, 0x77, 0x3d, 0xff, - 0x60, 0xee, 0x84, 0x76, 0xb3, 0x27, 0xd4, 0x91, 0x57, 0x96, 0xd8, 0x13, 0x12, 0x6d, 0x35, 0x67, - 0xba, 0xb6, 0x07, 0xb6, 0x4b, 0xf2, 0x3b, 0xab, 0xa5, 0x81, 0x0b, 0x54, 0xc6, 0x39, 0x89, 0xed, - 0x34, 0x95, 0x71, 0x8e, 0x0b, 0x5b, 0x00, 0x0e, 0xac, 0xf0, 0xc4, 0x33, 0x49, 0x6d, 0x8d, 0x17, - 0xf6, 0x40, 0x46, 0x69, 0x69, 0x4a, 0xec, 0x4e, 0x34, 0x09, 0x47, 0x6e, 0x48, 0xca, 0x6b, 0x56, - 0x8b, 0x92, 0xb8, 0xa5, 0xf9, 0x86, 0x3b, 0xb1, 0x82, 0x7a, 0x65, 0x3b, 0x7b, 0x3f, 0xa3, 0x89, - 0x94, 0xfa, 0x57, 0xd7, 0x21, 0xcf, 0x47, 0xf2, 0x15, 0x28, 0x1f, 0x93, 0xf3, 0x1f, 0x6d, 0x7b, - 0xe1, 0xa4, 0x27, 0x40, 0x6f, 0x3e, 0xe5, 0x9a, 0xa4, 0xf0, 0x0a, 0x65, 0x34, 0xfa, 0x8d, 0x59, - 0x7a, 0xf3, 0x10, 0xcb, 0xca, 0x12, 0x54, 0xa4, 0xb0, 0x12, 0xbe, 0x77, 0x46, 0xb3, 0x21, 0x47, - 0x88, 0x28, 0x49, 0xe7, 0x00, 0xb4, 0x3b, 0x22, 0x53, 0x9e, 0x70, 0x25, 0x02, 0x34, 0xdd, 0x70, - 0xd1, 0x83, 0x55, 0x58, 0xf2, 0x60, 0xb1, 0x5b, 0x80, 0x7a, 0xea, 0xc8, 0xea, 0xbb, 0x56, 0xb3, - 0x47, 0x3d, 0x5c, 0xd2, 0x24, 0x08, 0x2e, 0x10, 0xd3, 0x9b, 0x51, 0xa7, 0xe6, 0x35, 0xfc, 0xc9, - 0x3e, 0x8a, 0x67, 0x27, 0xb5, 0x51, 0x18, 0x00, 0x62, 0xf7, 0x90, 0xe7, 0xb1, 0x96, 0xa2, 0xc3, - 0x9c, 0x70, 0x4b, 0xe0, 0x06, 0x00, 0xfe, 0x54, 0xdb, 0x00, 0x9a, 0x77, 0x16, 0x58, 0x21, 0xb9, - 0x6a, 0xaf, 0x51, 0x13, 0x53, 0x47, 0x7e, 0xde, 0xd9, 0xa1, 0x17, 0xc4, 0x36, 0xf2, 0xfa, 0x6a, - 0x1b, 0x59, 0x7d, 0x17, 0x8a, 0xa8, 0x2f, 0x18, 0xa1, 0xc1, 0xee, 0x0a, 0xef, 0x18, 0xd7, 0x72, - 0x84, 0x9b, 0x30, 0x29, 0x43, 0xf8, 0xcb, 0xba, 0x51, 0xb9, 0xc4, 0xf3, 0x9a, 0x64, 0x85, 0xc6, - 0x7b, 0x95, 0xc8, 0x50, 0x68, 0x20, 0xaf, 0x40, 0x19, 0xab, 0x46, 0x67, 0x1b, 0x42, 0x2e, 0x94, - 0x7c, 0xef, 0xac, 0x89, 0x69, 0xf5, 0x5f, 0x67, 0xa0, 0xd2, 0xf7, 0x4d, 0xdc, 0x24, 0x07, 0x33, - 0x6b, 0xf4, 0x52, 0x93, 0x1e, 0xf5, 0x15, 0xcf, 0x71, 0x0c, 0x12, 0xc7, 0xc2, 0x0a, 0x8c, 0x01, - 0xec, 0x7d, 0xc8, 0x8d, 0x51, 0xec, 0x66, 0x65, 0x2d, 0x5e, 0xca, 0x3e, 0xfa, 0x8d, 0x82, 0x58, - 0x23, 0x52, 0xf5, 0x37, 0xe3, 0xf2, 0x49, 0x3a, 0xcb, 0xfe, 0xf9, 0x35, 0x3a, 0xb7, 0x1b, 0x34, - 0x95, 0x0c, 0x2b, 0x41, 0xae, 0xd5, 0x1e, 0x34, 0xb9, 0xee, 0x8e, 0x5a, 0xfc, 0x40, 0xdf, 0xed, - 0x68, 0x83, 0xa1, 0x92, 0xa3, 0x83, 0x40, 0x02, 0x74, 0x1b, 0x83, 0xa1, 0x52, 0x62, 0x00, 0x85, - 0xa3, 0x5e, 0xe7, 0x47, 0x47, 0x6d, 0x45, 0x51, 0xff, 0x79, 0x06, 0x20, 0x71, 0x23, 0xb3, 0xb7, - 0xa0, 0x72, 0x46, 0x29, 0x5d, 0x3a, 0xc1, 0x90, 0xdb, 0x08, 0x1c, 0x4d, 0xba, 0xd4, 0x3b, 0x50, - 0x8d, 0xb7, 0x15, 0xd4, 0x33, 0x96, 0x8f, 0x32, 0x2a, 0x31, 0x7e, 0xe7, 0x82, 0xbd, 0x0d, 0x25, - 0x0f, 0xdb, 0x81, 0xa4, 0x59, 0x59, 0xc9, 0x90, 0x9a, 0xaf, 0x15, 0x3d, 0x9e, 0x40, 0x7d, 0x64, - 0xec, 0x47, 0x56, 0x7d, 0x4c, 0xba, 0x8b, 0xa0, 0xa6, 0x63, 0xcc, 0x03, 0x4b, 0xe3, 0xf8, 0x58, - 0x4a, 0xe7, 0x13, 0x29, 0xad, 0xfe, 0x18, 0x36, 0x06, 0xc6, 0x74, 0xc6, 0x65, 0x39, 0x35, 0x8c, - 0x41, 0x0e, 0xe7, 0x84, 0x98, 0x7a, 0xf4, 0x1b, 0x17, 0xdd, 0xa1, 0xe5, 0x8f, 0x2c, 0x37, 0x5a, - 0xa3, 0x51, 0x12, 0xc5, 0xef, 0x11, 0x4a, 0x73, 0xcd, 0x3b, 0x8b, 0xc4, 0x79, 0x94, 0x56, 0xff, - 0x46, 0x06, 0x2a, 0x52, 0x35, 0xd8, 0xbb, 0x90, 0x23, 0xc5, 0x35, 0x23, 0x0b, 0x42, 0x89, 0x80, - 0xff, 0xe6, 0xaa, 0x0e, 0x12, 0xb2, 0x7b, 0x90, 0x0f, 0x42, 0xc3, 0x8f, 0x4e, 0x24, 0x14, 0x89, - 0x63, 0xc7, 0x9b, 0xbb, 0xa6, 0xc6, 0xd1, 0x4c, 0x85, 0xac, 0xe5, 0x9a, 0xc2, 0x0f, 0xb2, 0x4c, - 0x85, 0x48, 0x75, 0x1b, 0xca, 0x71, 0xf6, 0x38, 0x05, 0xb4, 0xfe, 0xd3, 0x81, 0xb2, 0xc6, 0xca, - 0x90, 0xd7, 0x1a, 0xbd, 0xbd, 0xb6, 0x92, 0x51, 0x7f, 0x96, 0x01, 0x48, 0xb8, 0xd8, 0xc3, 0x54, - 0x6d, 0x6f, 0x2c, 0xe6, 0xfa, 0x90, 0xfe, 0x4a, 0x95, 0xbd, 0x09, 0xe5, 0xb9, 0x4b, 0x40, 0xcb, - 0x14, 0x3b, 0x51, 0x02, 0x40, 0x6b, 0x2b, 0x8a, 0xe6, 0x59, 0xb0, 0xb6, 0x9e, 0x19, 0x8e, 0xfa, - 0x3d, 0x28, 0xc7, 0xd9, 0xa1, 0x01, 0xb9, 0xdb, 0xef, 0x76, 0xfb, 0x4f, 0x3b, 0xbd, 0x3d, 0x65, - 0x0d, 0x93, 0x87, 0x5a, 0xbb, 0xd9, 0x6e, 0x61, 0x32, 0x83, 0x73, 0xb6, 0x79, 0xa4, 0x69, 0xed, - 0xde, 0x50, 0xd7, 0xfa, 0x4f, 0x95, 0x75, 0xf5, 0x77, 0x33, 0x50, 0xeb, 0x3b, 0x68, 0xbb, 0x37, - 0x8d, 0x19, 0x6a, 0x26, 0xec, 0x07, 0xb0, 0x75, 0x3c, 0x47, 0xed, 0x78, 0xe6, 0x18, 0x23, 0xeb, - 0xc4, 0x73, 0x4c, 0x2b, 0x5a, 0x84, 0xa9, 0x33, 0x17, 0xe1, 0x1e, 0x56, 0x88, 0xf8, 0x30, 0xa1, - 0x65, 0x1f, 0x42, 0x75, 0xe6, 0x7b, 0xc7, 0x96, 0x1e, 0x78, 0x73, 0x7f, 0x64, 0x2d, 0x99, 0x75, - 0x09, 0x6f, 0x85, 0xe8, 0x06, 0x44, 0xa6, 0xfe, 0xad, 0x75, 0xa8, 0xb6, 0x2c, 0x73, 0x3e, 0xfb, - 0xcc, 0xb3, 0xdd, 0x66, 0x78, 0xce, 0x3e, 0x80, 0xaa, 0xe7, 0x90, 0xb7, 0x41, 0x97, 0xe2, 0x0d, - 0x56, 0xe5, 0x03, 0x1e, 0xb5, 0x80, 0xa2, 0x13, 0xde, 0x81, 0x4b, 0xdc, 0x80, 0x17, 0xae, 0xaf, - 0x73, 0xce, 0x8c, 0x6b, 0x26, 0xaf, 0x29, 0x1c, 0xc5, 0x37, 0x68, 0x22, 0xff, 0x35, 0xb8, 0x2c, - 0x91, 0xa3, 0x60, 0xe1, 0xf4, 0xd9, 0xa5, 0x35, 0xb6, 0x15, 0xf3, 0xc6, 0x91, 0x10, 0x9f, 0xc1, - 0xe5, 0xa8, 0x86, 0x23, 0xde, 0x7b, 0x9c, 0x39, 0x27, 0x9b, 0x21, 0xa9, 0xde, 0x15, 0x15, 0xde, - 0xf2, 0x64, 0x20, 0xe5, 0xf5, 0x3e, 0x5c, 0x31, 0xb1, 0xf5, 0x3a, 0xef, 0xfc, 0x53, 0xcb, 0x9a, - 0xe9, 0x8e, 0x11, 0x84, 0xe2, 0xb0, 0x94, 0x11, 0x72, 0x07, 0x71, 0x9f, 0x5b, 0xd6, 0xac, 0x6b, - 0x04, 0xa1, 0xfa, 0x97, 0xb2, 0x50, 0xe6, 0xde, 0x07, 0xec, 0xae, 0xfb, 0x50, 0xf4, 0x8e, 0xbf, - 0xd2, 0xfd, 0xd8, 0x2a, 0x5f, 0x3a, 0x10, 0x2d, 0x78, 0xc7, 0x5f, 0x69, 0xd6, 0x98, 0xbd, 0x15, - 0x6d, 0x75, 0x68, 0xc1, 0xaf, 0xcb, 0xee, 0xf6, 0x48, 0xfd, 0x17, 0x5b, 0x1f, 0x9a, 0xa6, 0x8f, - 0xa1, 0x62, 0xbb, 0x81, 0xe5, 0x87, 0xdc, 0xe3, 0x52, 0x7c, 0xfe, 0x20, 0x70, 0x32, 0x72, 0xc2, - 0x3c, 0x86, 0x0a, 0xf7, 0xc0, 0x70, 0xa6, 0xd2, 0xf3, 0x99, 0x38, 0x19, 0x31, 0x7d, 0x0a, 0x1b, - 0x89, 0x98, 0x23, 0xbe, 0xf2, 0x73, 0xf9, 0x6a, 0x31, 0x25, 0xb1, 0x3e, 0x82, 0xab, 0xc1, 0xa9, - 0x3d, 0xd3, 0x45, 0x4d, 0x3d, 0x97, 0x4e, 0x2a, 0xf4, 0xd9, 0xa9, 0x38, 0x21, 0x60, 0x88, 0xed, - 0x10, 0xb2, 0xef, 0xf6, 0xe6, 0x8e, 0x73, 0x78, 0xca, 0xde, 0x84, 0x2d, 0x41, 0x3e, 0x3b, 0x8d, - 0xe6, 0x0a, 0x19, 0xa5, 0x79, 0x6d, 0x83, 0x23, 0x0e, 0x4f, 0x85, 0x26, 0xf7, 0x31, 0xd4, 0xf9, - 0x19, 0xbd, 0x68, 0x94, 0x31, 0x1e, 0x5b, 0xa3, 0x50, 0x47, 0xd5, 0x41, 0x1c, 0x27, 0x5c, 0x21, - 0x3c, 0x77, 0x23, 0x35, 0x08, 0x8b, 0x92, 0x4f, 0xfd, 0xdb, 0x19, 0x28, 0xf3, 0x62, 0x71, 0x84, - 0x5e, 0x83, 0xec, 0x0b, 0x46, 0x07, 0x71, 0xec, 0x01, 0x6c, 0x19, 0xa6, 0x29, 0x0a, 0xb0, 0x4c, - 0x5e, 0x04, 0x17, 0x07, 0x9b, 0x86, 0x69, 0x36, 0x04, 0x9c, 0xc4, 0xea, 0x7d, 0x50, 0xec, 0x40, - 0x8f, 0xec, 0xcf, 0xe4, 0x44, 0xbe, 0xa4, 0x6d, 0xd8, 0x81, 0x30, 0x3f, 0xb9, 0xf7, 0x39, 0x35, - 0xe0, 0xb9, 0x17, 0x0f, 0xb8, 0xca, 0x00, 0x34, 0x8b, 0xd6, 0x7e, 0x33, 0x3c, 0xff, 0x2c, 0x57, - 0xca, 0x28, 0xa0, 0xfe, 0xc3, 0x0a, 0x54, 0x1a, 0xae, 0xe1, 0x5c, 0xfc, 0xc4, 0xa2, 0x70, 0x02, - 0xf2, 0x45, 0xce, 0xe6, 0xa2, 0x0b, 0xf8, 0x49, 0x58, 0x99, 0x20, 0x54, 0xb3, 0xdb, 0x50, 0xf1, - 0xe6, 0x61, 0x8c, 0xe7, 0x67, 0x63, 0xc0, 0x41, 0x44, 0x10, 0xf3, 0xc7, 0x7e, 0xee, 0x88, 0x9f, - 0xb4, 0xf2, 0x84, 0x3f, 0xd6, 0xd4, 0x62, 0x7e, 0x22, 0xb8, 0x03, 0xb5, 0xd0, 0x9e, 0x5a, 0x74, - 0x1c, 0x38, 0x9f, 0x5a, 0x26, 0x8f, 0x59, 0xe4, 0x61, 0x70, 0x4d, 0x01, 0xc3, 0x5c, 0xa6, 0xd6, - 0xd4, 0xf3, 0x2f, 0x78, 0x2e, 0x05, 0x9e, 0x0b, 0x07, 0x51, 0x2e, 0x6f, 0x03, 0x3b, 0x33, 0xec, - 0x50, 0x4f, 0x67, 0xc5, 0xb5, 0x63, 0x05, 0x31, 0x43, 0x39, 0xbb, 0xab, 0x50, 0x30, 0xed, 0xe0, - 0xb4, 0xd3, 0x17, 0x9a, 0xb1, 0x48, 0x61, 0x5b, 0x82, 0x91, 0x81, 0x1b, 0x73, 0x68, 0x71, 0x2d, - 0x2e, 0xab, 0x95, 0x11, 0xb2, 0x83, 0x00, 0x14, 0xec, 0xae, 0x15, 0x9e, 0x79, 0x3e, 0x72, 0x72, - 0xc5, 0x37, 0x01, 0xe0, 0x06, 0x88, 0xa4, 0x58, 0x10, 0xcd, 0xbd, 0xac, 0x16, 0xa7, 0x51, 0xa5, - 0xe4, 0xf3, 0x90, 0xb0, 0x55, 0x5e, 0xfd, 0x04, 0xc2, 0xee, 0xc2, 0x06, 0x55, 0x9f, 0x14, 0x63, - 0x6c, 0x03, 0x1d, 0x5f, 0x65, 0xb5, 0x2a, 0x42, 0xc9, 0x9e, 0x46, 0xaa, 0x4f, 0xe1, 0x7a, 0xaa, - 0x7d, 0xba, 0xe1, 0xfb, 0xc6, 0x85, 0x3e, 0x35, 0xbe, 0xf2, 0x7c, 0xf2, 0x7d, 0x64, 0xb5, 0xab, - 0x72, 0xb7, 0x35, 0x10, 0x7d, 0x80, 0xd8, 0xe7, 0xb2, 0xda, 0xae, 0xe7, 0x93, 0x63, 0x64, 0x25, - 0x2b, 0x62, 0xc9, 0x8a, 0xa7, 0x01, 0x26, 0x2d, 0x3d, 0xe0, 0xe1, 0x93, 0x5a, 0x85, 0x60, 0x3b, - 0x04, 0x42, 0x3d, 0x35, 0x78, 0xcc, 0xe5, 0xe5, 0x96, 0x88, 0x65, 0x7a, 0x4c, 0x92, 0x90, 0x23, - 0x4e, 0x2c, 0xc3, 0xa4, 0x23, 0x31, 0x42, 0xec, 0x5b, 0x06, 0x1d, 0x38, 0x07, 0x8f, 0xf5, 0xd9, - 0x3c, 0xe4, 0x71, 0x8f, 0x5a, 0x3e, 0x78, 0x7c, 0x38, 0x0f, 0x05, 0x78, 0x62, 0x85, 0x74, 0x02, - 0x46, 0xe0, 0x3d, 0x2b, 0x44, 0x65, 0x33, 0x78, 0x1c, 0x39, 0x82, 0xaf, 0x88, 0xbe, 0x7d, 0x2c, - 0x3c, 0xbd, 0x2a, 0xd4, 0x62, 0xa4, 0x3e, 0x9d, 0xf3, 0x40, 0xc7, 0xac, 0x56, 0x89, 0x08, 0x0e, - 0xe6, 0x0e, 0x0e, 0xec, 0xc8, 0x18, 0x9d, 0x58, 0xba, 0x8f, 0x55, 0xb9, 0xc6, 0x87, 0x8e, 0x20, - 0x1a, 0xd6, 0xe6, 0x15, 0xe0, 0x09, 0xfd, 0xc4, 0x0e, 0xc9, 0x41, 0x93, 0xd5, 0x4a, 0x04, 0xd8, - 0xb7, 0x43, 0x5c, 0xc7, 0x1c, 0x29, 0x66, 0x20, 0x65, 0x71, 0x9d, 0x88, 0x36, 0x09, 0x71, 0x40, - 0x70, 0xca, 0xe8, 0x3e, 0x28, 0x29, 0x5a, 0xcc, 0xef, 0x06, 0x91, 0x6e, 0x48, 0xa4, 0x98, 0xeb, - 0x3d, 0xe0, 0xcc, 0x3a, 0x4e, 0x3d, 0x9e, 0xe7, 0x2b, 0xdc, 0x4a, 0x23, 0x70, 0xcb, 0x0e, 0x4e, - 0x29, 0xc7, 0xbb, 0xb0, 0x21, 0xd1, 0x61, 0x7e, 0x37, 0xf9, 0xcc, 0x88, 0xc9, 0x52, 0x75, 0xf4, - 0xad, 0xa9, 0x17, 0x8a, 0x66, 0xbe, 0x2a, 0xd5, 0x51, 0x23, 0x78, 0xba, 0x8e, 0x82, 0x16, 0xf3, - 0xbc, 0x25, 0xd5, 0x91, 0x93, 0x62, 0xae, 0xaf, 0x41, 0xf5, 0xcc, 0xb7, 0xc3, 0xd0, 0x72, 0xf9, - 0xe2, 0xbf, 0xcd, 0x3b, 0x56, 0xc0, 0x68, 0xf5, 0xbf, 0x06, 0x55, 0xde, 0xf3, 0x42, 0xbe, 0x6d, - 0x73, 0x12, 0x01, 0x8b, 0x04, 0x84, 0xe8, 0x8d, 0xa9, 0xed, 0x92, 0x17, 0x26, 0xab, 0x95, 0x39, - 0x04, 0x8d, 0x55, 0x09, 0x6d, 0x9c, 0x93, 0x2f, 0x26, 0x41, 0x1b, 0xe7, 0xb4, 0x24, 0x67, 0xb6, - 0xe3, 0xf0, 0x85, 0x7f, 0x47, 0x2c, 0x49, 0x84, 0xd0, 0xba, 0x8f, 0xd1, 0x54, 0xfa, 0x5d, 0x09, - 0x4d, 0x65, 0xe3, 0xc4, 0x21, 0x34, 0x16, 0xfd, 0xba, 0x98, 0x38, 0x08, 0xc0, 0x92, 0x13, 0xa4, - 0x71, 0x4e, 0x9e, 0x96, 0x18, 0x69, 0x9c, 0x0b, 0xb9, 0x85, 0xb9, 0x12, 0xef, 0x1b, 0xb1, 0xdc, - 0x42, 0x10, 0x72, 0xcb, 0x04, 0xc6, 0x79, 0xfd, 0x7e, 0x9a, 0xc0, 0x38, 0x27, 0x0b, 0xc9, 0x32, - 0x4c, 0x5e, 0xf1, 0x37, 0x79, 0xf6, 0x08, 0xa0, 0x7a, 0x6f, 0x43, 0x35, 0x78, 0xac, 0x27, 0xf8, - 0x07, 0x9c, 0x3d, 0x78, 0xac, 0x45, 0x14, 0x77, 0x61, 0x23, 0x9e, 0x1a, 0x9c, 0xe6, 0x2d, 0x3e, - 0xf0, 0xa6, 0x98, 0x1a, 0x74, 0x82, 0xf8, 0xd3, 0x0c, 0xdc, 0xe8, 0x93, 0x13, 0x89, 0xc4, 0xff, - 0x81, 0x15, 0x04, 0xc6, 0xc4, 0xda, 0xf5, 0xfc, 0xdd, 0xf9, 0x4f, 0x7e, 0x72, 0xc1, 0xee, 0xc3, - 0xe6, 0xa1, 0xe1, 0x5b, 0x6e, 0x18, 0x9f, 0x7d, 0x09, 0xcf, 0xcc, 0x22, 0x98, 0x7d, 0x02, 0x0a, - 0x07, 0xf1, 0x38, 0xae, 0x66, 0x74, 0x9e, 0xb3, 0xe8, 0x7f, 0x5e, 0xa2, 0x42, 0x63, 0xaf, 0xdc, - 0xb2, 0x83, 0x50, 0x43, 0x4b, 0x9f, 0x7d, 0x0a, 0x8a, 0xe3, 0x9d, 0xa1, 0xc5, 0x82, 0x6a, 0xac, - 0x2e, 0x29, 0xce, 0x62, 0x97, 0x4c, 0xb4, 0xe5, 0x0d, 0x22, 0x4c, 0xd4, 0xdd, 0x4f, 0x41, 0x99, - 0xcf, 0x66, 0x69, 0xd6, 0xf5, 0xe7, 0xb0, 0x12, 0x61, 0xc2, 0xfa, 0x16, 0x54, 0xa4, 0x52, 0x57, - 0x28, 0xd7, 0x90, 0x94, 0x85, 0xc4, 0x52, 0x39, 0x2b, 0xe2, 0x48, 0x21, 0xc9, 0x5d, 0xfd, 0xbf, - 0xd7, 0x41, 0x21, 0xe7, 0x98, 0x46, 0xe7, 0xff, 0x74, 0x84, 0x96, 0x32, 0xcb, 0x32, 0x2f, 0x35, - 0xcb, 0xb6, 0x21, 0xef, 0xd8, 0xd3, 0x38, 0x9c, 0x2a, 0x75, 0xb0, 0x43, 0x08, 0x1c, 0x6b, 0xcf, - 0xb7, 0x27, 0x64, 0x40, 0xca, 0xa1, 0x85, 0xe4, 0x1f, 0x44, 0x7b, 0x8c, 0x86, 0xe8, 0x21, 0x80, - 0x69, 0x07, 0xa1, 0x4e, 0x2e, 0x15, 0x51, 0x6d, 0xd1, 0x33, 0x71, 0xff, 0x6b, 0x65, 0x33, 0x1e, - 0x8a, 0xfb, 0xa0, 0x48, 0x4a, 0x98, 0x1b, 0x7b, 0x43, 0xf2, 0x5a, 0xa2, 0x9c, 0x35, 0xdd, 0xa6, - 0x1b, 0x2e, 0x51, 0xa2, 0xfa, 0x54, 0x58, 0xa2, 0xec, 0x98, 0xe7, 0xea, 0x9f, 0xaa, 0x90, 0xeb, - 0x79, 0xa6, 0xc5, 0xde, 0x83, 0x32, 0xc5, 0xeb, 0x4a, 0x03, 0x2c, 0x94, 0x64, 0x44, 0xd3, 0x1f, - 0x1a, 0xa9, 0x92, 0x2b, 0x7e, 0x3d, 0x3f, 0xc2, 0xf7, 0x35, 0x32, 0xee, 0xe8, 0x94, 0x17, 0x9b, - 0x54, 0x11, 0x0e, 0x28, 0xf2, 0x97, 0x70, 0x0c, 0xee, 0xad, 0xe4, 0xe6, 0xf7, 0x2d, 0x97, 0x34, - 0xf2, 0xbc, 0x16, 0xa7, 0xc9, 0xa4, 0xf6, 0x3d, 0x54, 0xbd, 0xf8, 0x0e, 0x94, 0x5f, 0x61, 0x52, - 0x73, 0x3c, 0x6d, 0x49, 0xef, 0x41, 0xf9, 0x2b, 0xcf, 0x76, 0x79, 0xc5, 0x0b, 0x4b, 0x15, 0x47, - 0x8b, 0x85, 0x57, 0xfc, 0x2b, 0xf1, 0x8b, 0xdd, 0x81, 0xa2, 0xe7, 0xf2, 0xbc, 0x8b, 0x4b, 0x79, - 0x17, 0x3c, 0xb7, 0xcb, 0x03, 0xb6, 0x6a, 0x76, 0xa0, 0xfb, 0xf6, 0xe4, 0x24, 0xd4, 0x91, 0x53, - 0x9c, 0x0e, 0x57, 0xec, 0x40, 0x43, 0x18, 0x66, 0x8b, 0x13, 0x6f, 0x6c, 0x3b, 0xa8, 0xe1, 0x51, - 0x66, 0xe5, 0xa5, 0xcc, 0x80, 0xa3, 0x29, 0xc3, 0xd7, 0xa1, 0x34, 0xf1, 0x3d, 0x34, 0x22, 0x2e, - 0xea, 0xb0, 0x44, 0x59, 0x24, 0xdc, 0xce, 0x05, 0xaa, 0x4f, 0xf4, 0xd3, 0x76, 0x27, 0x3a, 0x79, - 0x49, 0x2a, 0xdb, 0xd9, 0xfb, 0x25, 0xad, 0x1a, 0x01, 0xc9, 0xff, 0xf1, 0x3a, 0x94, 0x8c, 0xc9, - 0x44, 0x17, 0x71, 0x67, 0x4b, 0x79, 0x19, 0x93, 0x09, 0x15, 0xf9, 0x10, 0x6a, 0x67, 0xb6, 0xab, - 0x07, 0x33, 0x6b, 0xc4, 0x69, 0x6b, 0xcb, 0x5d, 0x79, 0x66, 0xbb, 0x38, 0xbb, 0x89, 0x5e, 0x5e, - 0x06, 0x1b, 0xdf, 0x7c, 0x19, 0x6c, 0x3e, 0x6f, 0x19, 0xa8, 0x50, 0x10, 0x47, 0x00, 0xca, 0x12, - 0x89, 0xc0, 0xb0, 0xf7, 0xa1, 0xe2, 0x1b, 0xee, 0xa9, 0x2e, 0x8e, 0xda, 0xbf, 0x94, 0x2d, 0x7d, - 0xcd, 0x70, 0x4f, 0xc5, 0x49, 0x3b, 0xf8, 0xf1, 0xef, 0xb4, 0xca, 0xbc, 0xf5, 0x12, 0x1b, 0x49, - 0x32, 0xbd, 0xd8, 0x8b, 0x4d, 0xaf, 0x0f, 0xc9, 0xc6, 0xb1, 0xdc, 0x50, 0x8f, 0x18, 0x2e, 0xad, - 0x66, 0xa8, 0x72, 0xb2, 0x3e, 0x67, 0xc3, 0x06, 0x90, 0xa7, 0x4d, 0x27, 0xb7, 0xdc, 0xe5, 0x54, - 0x03, 0x62, 0x17, 0x9c, 0x06, 0x7e, 0xe2, 0x8e, 0x6b, 0xc0, 0x66, 0x12, 0xee, 0xcb, 0x63, 0xac, - 0xaf, 0xc8, 0xae, 0xfe, 0x54, 0x7c, 0x70, 0x64, 0x55, 0xd9, 0xa9, 0xa0, 0xe1, 0x3b, 0x50, 0xe3, - 0xb1, 0x2d, 0xbc, 0xdf, 0x02, 0x52, 0x92, 0xca, 0x5a, 0x95, 0x80, 0xbc, 0x9f, 0x02, 0x12, 0x30, - 0xc2, 0xd4, 0x0b, 0xcf, 0x49, 0x4b, 0x4a, 0x04, 0x0c, 0xb7, 0xed, 0xc2, 0x73, 0xad, 0x6c, 0x46, - 0x3f, 0x71, 0xf3, 0x3f, 0xb6, 0x5d, 0x13, 0xa7, 0x5e, 0x68, 0x4c, 0x82, 0x7a, 0x9d, 0x56, 0x66, - 0x45, 0xc0, 0x86, 0xc6, 0x24, 0x40, 0xc3, 0xdf, 0xe0, 0xc6, 0x06, 0xaf, 0xf7, 0x75, 0xd9, 0x33, - 0x25, 0x99, 0x21, 0x5a, 0xc5, 0x90, 0x6c, 0x92, 0x8f, 0x81, 0x45, 0x27, 0x94, 0x92, 0x1d, 0x7f, - 0x63, 0x69, 0x36, 0x6e, 0x8a, 0x23, 0xca, 0xd8, 0x8a, 0xbf, 0x0d, 0x15, 0xee, 0xa9, 0xd0, 0x83, - 0xd0, 0x9a, 0xd5, 0x5f, 0xa1, 0x0a, 0x01, 0x07, 0x0d, 0x42, 0x6b, 0xc6, 0x3e, 0x86, 0x5a, 0xda, - 0xca, 0xba, 0xb9, 0xe2, 0xa0, 0x8f, 0xa6, 0x85, 0x56, 0x1d, 0xc9, 0x76, 0xd7, 0x1d, 0x1e, 0xb6, - 0x4e, 0x1a, 0x12, 0x31, 0xf2, 0xc3, 0xac, 0xaa, 0xeb, 0x85, 0xcd, 0x08, 0x86, 0x1d, 0x18, 0x19, - 0xd8, 0xe1, 0x39, 0x29, 0x55, 0x71, 0x07, 0xc6, 0xa6, 0x23, 0x1a, 0x47, 0x91, 0x15, 0xd9, 0x02, - 0x1e, 0xcb, 0x41, 0x9b, 0xbc, 0xe5, 0xf3, 0xb8, 0x0d, 0xd2, 0xa1, 0xe2, 0xb3, 0xbf, 0xc5, 0xbd, - 0x47, 0xe3, 0x31, 0x2e, 0xf2, 0x6e, 0xf4, 0x09, 0x6c, 0xcc, 0x7c, 0x4b, 0x97, 0x4a, 0x56, 0xe5, - 0x46, 0x1d, 0xfa, 0x56, 0x52, 0x78, 0x75, 0x26, 0xa5, 0xd8, 0x0f, 0x60, 0x4b, 0xe2, 0x9c, 0x9f, - 0x12, 0xf3, 0x1d, 0x62, 0xbe, 0xbc, 0xc0, 0x7c, 0x74, 0x8a, 0xec, 0x1b, 0xb3, 0x54, 0x9a, 0x5d, - 0x86, 0x7c, 0x27, 0x68, 0xbb, 0x26, 0xe9, 0x56, 0x25, 0x8d, 0x27, 0xd8, 0x63, 0xa8, 0x72, 0x43, - 0x86, 0x42, 0x5f, 0x83, 0xfa, 0x3d, 0xd9, 0xf5, 0x4c, 0xd6, 0x0c, 0x21, 0xb4, 0x8a, 0x13, 0xff, - 0x0e, 0xd8, 0x47, 0xb0, 0xc5, 0xcf, 0x05, 0x64, 0x11, 0xf9, 0xc6, 0xf2, 0x90, 0x13, 0xd1, 0x6e, - 0x22, 0x27, 0x35, 0xb8, 0xee, 0xcf, 0x5d, 0x32, 0x6e, 0x04, 0x27, 0xf7, 0x58, 0x11, 0xff, 0x7d, - 0xe2, 0xbf, 0x26, 0x56, 0x17, 0x27, 0xe3, 0xbc, 0x24, 0x9b, 0xae, 0xfa, 0x32, 0xe8, 0x10, 0xf9, - 0x9e, 0x93, 0x27, 0xf7, 0xe4, 0x50, 0x9e, 0x6f, 0x7e, 0x9b, 0x3c, 0xc9, 0xcb, 0x43, 0x79, 0x32, - 0xc8, 0xcd, 0xe7, 0xb6, 0x49, 0x9a, 0x5e, 0x55, 0xa3, 0xdf, 0xec, 0x75, 0xd8, 0xf0, 0xad, 0xd1, - 0xdc, 0x0f, 0xec, 0x67, 0x96, 0x1e, 0xd8, 0xee, 0x29, 0xe9, 0x78, 0x25, 0xad, 0x16, 0x43, 0x07, - 0xb6, 0x7b, 0x8a, 0x22, 0xc3, 0x3a, 0x0f, 0x2d, 0xdf, 0xe5, 0xf1, 0xfe, 0x6f, 0xcb, 0x22, 0xa3, - 0x4d, 0x08, 0x5c, 0xe7, 0x1a, 0x58, 0xf1, 0xef, 0x85, 0x91, 0x0d, 0xf8, 0xc8, 0x3e, 0xfc, 0x46, - 0x23, 0x3b, 0xa0, 0x91, 0xbd, 0x07, 0x25, 0xdb, 0x0d, 0x2d, 0xff, 0x99, 0xe1, 0xd4, 0xdf, 0x5d, - 0x92, 0xc6, 0x31, 0x8e, 0xdd, 0x85, 0x62, 0xe0, 0xd8, 0xb8, 0xde, 0xeb, 0xef, 0x2d, 0x91, 0x45, - 0x28, 0x76, 0x1f, 0xca, 0xf1, 0xb5, 0xb6, 0xfa, 0xfb, 0x4b, 0x74, 0x09, 0x92, 0xdd, 0x82, 0xdc, - 0x19, 0x4e, 0xa8, 0x47, 0xcb, 0x47, 0x05, 0x08, 0xc7, 0xed, 0x7b, 0x8c, 0x3a, 0x3b, 0x6d, 0xdf, - 0x8f, 0x97, 0xb6, 0xef, 0x5d, 0xdb, 0x71, 0xf8, 0xf6, 0x3d, 0x16, 0xbf, 0x70, 0xf3, 0x23, 0x0e, - 0x6c, 0xc9, 0x07, 0xcb, 0x9b, 0x1f, 0xe2, 0x9e, 0xd0, 0x05, 0xc0, 0x4a, 0x40, 0xfe, 0x6f, 0xee, - 0xc6, 0xff, 0x50, 0xee, 0xab, 0xb4, 0x63, 0x5c, 0x83, 0x20, 0x4e, 0xa3, 0x01, 0x22, 0xbc, 0xff, - 0xa8, 0x34, 0x7d, 0xc4, 0x6f, 0x8b, 0x70, 0x48, 0xc7, 0x3c, 0x67, 0xef, 0x41, 0x2d, 0x0a, 0x2b, - 0xc2, 0xe2, 0x82, 0xfa, 0xc7, 0x4b, 0x35, 0x48, 0x13, 0xb0, 0x16, 0x54, 0xc7, 0xa8, 0xbb, 0x4f, - 0xb9, 0x2a, 0x5f, 0xff, 0x84, 0x2a, 0xb2, 0x1d, 0x6d, 0xac, 0xcf, 0x53, 0xf5, 0xb5, 0x14, 0x17, - 0x7b, 0x08, 0xcc, 0x1e, 0xf3, 0xf1, 0xdc, 0xf5, 0xbd, 0x29, 0x57, 0xd7, 0xeb, 0x9f, 0x72, 0x0f, - 0xda, 0x32, 0x86, 0x0e, 0x03, 0x2d, 0xd7, 0xd4, 0xa7, 0x81, 0x50, 0x13, 0xbe, 0x47, 0xf5, 0x14, - 0xc2, 0x2b, 0xbe, 0xfe, 0x1a, 0x39, 0x7b, 0x91, 0xf6, 0x20, 0xe0, 0x5a, 0xc3, 0xa7, 0x80, 0xd3, - 0xf5, 0x59, 0xc2, 0xfa, 0x6b, 0x2f, 0x64, 0x45, 0xda, 0x88, 0xf5, 0x13, 0xd8, 0xe0, 0x8e, 0x52, - 0xd2, 0xc8, 0x70, 0x8a, 0x7e, 0x5f, 0x96, 0x5c, 0xb2, 0x0b, 0x59, 0xab, 0x9a, 0xb2, 0x43, 0xf9, - 0x63, 0xd8, 0x8c, 0x7c, 0xbd, 0xa1, 0x70, 0x0b, 0xff, 0xba, 0x5c, 0x6c, 0xec, 0x4b, 0xd5, 0x6a, - 0xf3, 0xe8, 0x27, 0x15, 0xf9, 0x18, 0x6a, 0xb4, 0x8b, 0xc6, 0xf7, 0x8b, 0x7e, 0x63, 0xe5, 0xfd, - 0xa2, 0x2a, 0x12, 0x45, 0x29, 0x14, 0xfe, 0xc9, 0x3a, 0x1d, 0x85, 0x56, 0xfd, 0x07, 0x5c, 0xf8, - 0xc7, 0xc0, 0x66, 0x68, 0xb1, 0xc7, 0x00, 0xc6, 0x6c, 0xe6, 0x5c, 0xf0, 0xa9, 0xf9, 0x43, 0x9a, - 0x9a, 0x97, 0xa5, 0xa9, 0xd9, 0x40, 0x24, 0xcd, 0xcd, 0xb2, 0x11, 0xfd, 0x64, 0x8f, 0xa0, 0x3a, - 0xf3, 0x82, 0x50, 0x37, 0xa7, 0x0e, 0xb5, 0xbf, 0x21, 0xaf, 0xed, 0x43, 0x2f, 0x08, 0x5b, 0x53, - 0x07, 0x5b, 0x01, 0xb3, 0xf8, 0x37, 0xeb, 0xc2, 0x25, 0xcf, 0xd5, 0xcd, 0xf9, 0xcc, 0xb1, 0x47, - 0xd8, 0x03, 0x06, 0x1d, 0xbd, 0xd7, 0x77, 0xa8, 0xc4, 0x9b, 0x52, 0x89, 0x7d, 0xb7, 0x15, 0x11, - 0x89, 0x18, 0xb7, 0x2d, 0x6f, 0x11, 0x44, 0x76, 0x26, 0x8d, 0x41, 0x1c, 0x13, 0xda, 0xe4, 0xaa, - 0x01, 0x41, 0xa3, 0xa0, 0xd0, 0x4f, 0x60, 0x33, 0xa1, 0xc2, 0x06, 0x06, 0xf5, 0x96, 0x3c, 0x93, - 0xa5, 0x20, 0xf3, 0x5a, 0xc4, 0x88, 0xb0, 0x80, 0xfa, 0xce, 0x73, 0x9c, 0xf9, 0x4c, 0x88, 0xd2, - 0x7a, 0x5b, 0xf4, 0x1d, 0x01, 0xb9, 0x94, 0x94, 0x4c, 0x71, 0x6b, 0x5a, 0xdf, 0x95, 0x4d, 0x71, - 0x6b, 0x8a, 0x6a, 0x86, 0x08, 0xf8, 0x7d, 0x66, 0x5b, 0x67, 0x41, 0x7d, 0x8f, 0x62, 0x15, 0x45, - 0x48, 0xf5, 0x13, 0x04, 0xe1, 0xbe, 0x6f, 0xda, 0x3e, 0x9a, 0x00, 0x14, 0x9c, 0xb5, 0xcf, 0x63, - 0x72, 0x39, 0x08, 0x29, 0x58, 0x13, 0xea, 0x24, 0xce, 0xc5, 0x5a, 0x4d, 0x9d, 0xc1, 0x75, 0x96, - 0x96, 0xe4, 0x15, 0xa4, 0xe5, 0x47, 0x7c, 0x87, 0xd2, 0x69, 0xdc, 0x0f, 0xe1, 0xd5, 0xd5, 0x99, - 0x44, 0xd7, 0x2f, 0x3f, 0x23, 0x7d, 0xe3, 0xfa, 0x0a, 0xee, 0x26, 0xdd, 0xc8, 0x54, 0x7f, 0x96, - 0x87, 0x52, 0x64, 0x1a, 0xb1, 0x0a, 0x14, 0x8f, 0x7a, 0x9f, 0xf7, 0xfa, 0x4f, 0x7b, 0xfc, 0x16, - 0x62, 0x63, 0x30, 0x68, 0x6b, 0x43, 0xc5, 0x64, 0x1b, 0x00, 0x74, 0xb3, 0x47, 0x1f, 0x34, 0x1b, - 0x3d, 0x7e, 0x2b, 0x91, 0xee, 0x13, 0xf1, 0xf4, 0x3a, 0xdb, 0x82, 0xda, 0xee, 0x51, 0x8f, 0xe2, - 0x0a, 0x39, 0x28, 0x8b, 0xa0, 0xf6, 0x17, 0xfc, 0x84, 0x93, 0x83, 0x72, 0x08, 0x3a, 0x68, 0x0c, - 0xdb, 0x5a, 0x27, 0x02, 0xe5, 0x29, 0x44, 0xb1, 0x7f, 0xa4, 0x35, 0x45, 0x4e, 0x05, 0x76, 0x05, - 0xb6, 0x62, 0xb6, 0x28, 0x4b, 0xa5, 0x88, 0x35, 0x3b, 0xd4, 0xfa, 0x9f, 0xb5, 0x9b, 0x43, 0x05, - 0xe8, 0xb8, 0x74, 0x6f, 0x4f, 0xa9, 0xb0, 0x2a, 0x94, 0x5a, 0x9d, 0xc1, 0xb0, 0xd3, 0x6b, 0x0e, - 0x95, 0x2a, 0x56, 0x78, 0xb7, 0xd3, 0x1d, 0xb6, 0x35, 0xa5, 0xc6, 0x4a, 0x90, 0xfb, 0xac, 0xdf, - 0xe9, 0x29, 0x1b, 0x74, 0xc3, 0xa9, 0x71, 0x70, 0xd8, 0x6d, 0x2b, 0x9b, 0x08, 0x1d, 0xf4, 0xb5, - 0xa1, 0xa2, 0x20, 0xf4, 0x69, 0xa7, 0xd7, 0xea, 0x3f, 0x55, 0xb6, 0x58, 0x19, 0xf2, 0x47, 0x3d, - 0x2c, 0x86, 0xb1, 0x1a, 0x94, 0xe9, 0xa7, 0xde, 0xe8, 0x76, 0x95, 0x4b, 0xd2, 0x19, 0xeb, 0x65, - 0x44, 0xd1, 0x89, 0xed, 0x00, 0xeb, 0x70, 0x05, 0xdb, 0x12, 0x27, 0x89, 0xfa, 0x2a, 0xe6, 0x73, - 0xd0, 0xe9, 0x1d, 0x0d, 0x94, 0x6b, 0x48, 0x4c, 0x3f, 0x09, 0x53, 0xc7, 0x7c, 0x3a, 0x3d, 0xea, - 0xca, 0x5b, 0xf8, 0xbb, 0xd5, 0xee, 0xb6, 0x87, 0x6d, 0xe5, 0x36, 0xb6, 0xaa, 0xdb, 0x6f, 0x7e, - 0xae, 0xf7, 0x0f, 0x95, 0xd7, 0xb0, 0x4f, 0x0f, 0xb5, 0xb6, 0x2e, 0x08, 0xef, 0x30, 0x05, 0xaa, - 0xbb, 0x47, 0x3f, 0xfe, 0xf1, 0x97, 0xba, 0x68, 0xd4, 0xeb, 0x58, 0x66, 0x42, 0xa1, 0x1f, 0x7d, - 0xae, 0xdc, 0x5b, 0x00, 0x0d, 0x3e, 0x57, 0xde, 0xc0, 0x4e, 0x89, 0x7a, 0x59, 0xb9, 0x8f, 0x04, - 0x5a, 0xbb, 0x79, 0xa4, 0x0d, 0x3a, 0x4f, 0xda, 0x7a, 0x73, 0xd8, 0x56, 0xde, 0xa4, 0x5e, 0xe8, - 0xf4, 0x3e, 0x57, 0x1e, 0x60, 0x35, 0xf1, 0x17, 0xef, 0xfb, 0xb7, 0x18, 0x83, 0x8d, 0x84, 0x96, - 0x60, 0x6f, 0x23, 0xc9, 0x8e, 0xd6, 0x6f, 0xb4, 0x9a, 0x8d, 0xc1, 0x50, 0x79, 0x07, 0xdb, 0x38, - 0x38, 0xec, 0x76, 0x86, 0xca, 0x43, 0x6c, 0xc8, 0x5e, 0x63, 0xb8, 0xdf, 0xd6, 0x94, 0x77, 0x71, - 0x18, 0x87, 0x9d, 0x83, 0xb6, 0x2e, 0xfa, 0xf4, 0x11, 0x96, 0xb1, 0xdb, 0xe9, 0x76, 0x95, 0xc7, - 0x74, 0x46, 0xd8, 0xd0, 0x86, 0x1d, 0x1a, 0xc8, 0x0f, 0x30, 0x83, 0xc6, 0xe1, 0x61, 0xf7, 0x4b, - 0xe5, 0x43, 0x6c, 0xe0, 0xc1, 0x51, 0x77, 0xd8, 0xd1, 0x8f, 0x0e, 0x5b, 0x8d, 0x61, 0x5b, 0xf9, - 0x88, 0x46, 0xb9, 0x3f, 0x18, 0xb6, 0x0e, 0xba, 0xca, 0xc7, 0x94, 0x27, 0xcd, 0xb1, 0x66, 0xb7, - 0xdf, 0x6b, 0x2b, 0x9f, 0xa8, 0xb9, 0xd2, 0xb6, 0xb2, 0xad, 0xe6, 0x4a, 0xaa, 0xa2, 0xaa, 0xbf, - 0x0d, 0xa5, 0xc8, 0x2e, 0xc6, 0x2c, 0x3b, 0xbd, 0x5e, 0x5b, 0x53, 0xd6, 0xb0, 0xd8, 0x6e, 0x7b, - 0x77, 0xa8, 0x64, 0xe8, 0xf0, 0xb4, 0xb3, 0xb7, 0x3f, 0x54, 0xd6, 0xf1, 0x67, 0xff, 0x08, 0x7b, + 0x0c, 0xe9, 0x73, 0xe0, 0x0e, 0xb2, 0x3c, 0x69, 0x68, 0x0d, 0xed, 0xb1, 0xf2, 0x43, 0x56, 0x82, + 0x6c, 0xe3, 0xf1, 0x63, 0xe5, 0xa7, 0x28, 0xa7, 0xca, 0x4f, 0x3b, 0x3d, 0xfd, 0x49, 0xa3, 0x7b, + 0xd4, 0x56, 0x7e, 0xba, 0x1e, 0xa5, 0xfb, 0x5a, 0xab, 0xad, 0x29, 0x3f, 0xcd, 0xb1, 0x2d, 0xa8, + 0xfe, 0xb8, 0xdf, 0x6b, 0x1f, 0x34, 0x0e, 0x0f, 0xa9, 0x22, 0x3f, 0x2d, 0xa9, 0xff, 0x31, 0x07, + 0x39, 0xec, 0x2b, 0xa6, 0x26, 0x32, 0x39, 0xee, 0x44, 0x6c, 0xdc, 0x6e, 0xee, 0x4f, 0xfe, 0xfc, + 0xd6, 0x1a, 0x97, 0xc6, 0xaf, 0x41, 0xd6, 0xb1, 0x43, 0x9a, 0x38, 0xf1, 0x4a, 0x16, 0x1a, 0xec, + 0xfe, 0x9a, 0x86, 0x38, 0x76, 0x13, 0x32, 0x5c, 0x2c, 0xc7, 0x8b, 0x33, 0xda, 0xd7, 0xf7, 0xd7, + 0xb4, 0xcc, 0x8c, 0xdd, 0x80, 0xcc, 0x33, 0x21, 0xa3, 0xab, 0x1c, 0xcf, 0x77, 0x76, 0xc4, 0x3e, + 0x63, 0x3b, 0x90, 0x1d, 0x79, 0x5c, 0x3f, 0x8d, 0xf1, 0x7c, 0x9f, 0xc3, 0xfc, 0x47, 0x9e, 0xc3, + 0x6e, 0x43, 0xd6, 0x37, 0xce, 0x68, 0xee, 0xc4, 0x13, 0x22, 0xde, 0x48, 0x91, 0xc8, 0x37, 0xce, + 0xb0, 0x12, 0x63, 0x92, 0x6a, 0x71, 0x25, 0xa2, 0x19, 0x85, 0xc5, 0x8c, 0xd9, 0x0e, 0x64, 0xce, + 0x48, 0xae, 0xc5, 0x2a, 0xd9, 0x53, 0xdb, 0x35, 0xbd, 0xb3, 0xc1, 0xcc, 0x1a, 0x21, 0xc5, 0x19, + 0x7b, 0x1d, 0xb2, 0xc1, 0xfc, 0x98, 0xe4, 0x5a, 0xe5, 0xe1, 0xd6, 0xd2, 0x0e, 0x85, 0x05, 0x05, + 0xf3, 0x63, 0x76, 0x17, 0x72, 0x23, 0xcf, 0xf7, 0x85, 0x6c, 0x53, 0xa2, 0x0a, 0x47, 0x9b, 0x33, + 0xaa, 0xa8, 0x88, 0xc7, 0x02, 0x43, 0x92, 0x68, 0x31, 0x51, 0xb2, 0x3b, 0x62, 0x81, 0x21, 0xbb, + 0x23, 0xb6, 0xdc, 0x94, 0x5c, 0x8b, 0x36, 0x64, 0xcc, 0x07, 0xb1, 0x38, 0x48, 0x53, 0xe3, 0x9c, + 0xf4, 0xdf, 0x98, 0x28, 0xda, 0x89, 0xb1, 0x4e, 0x53, 0xe3, 0x9c, 0xdd, 0x81, 0xec, 0x33, 0x6b, + 0x44, 0xaa, 0x70, 0x5c, 0x9a, 0x18, 0xa4, 0x27, 0xd4, 0x3c, 0x44, 0xd3, 0xca, 0xf2, 0x1c, 0x93, + 0xb4, 0xe2, 0x78, 0x2c, 0xf7, 0x3c, 0xc7, 0x7c, 0x42, 0x63, 0x49, 0x48, 0x54, 0x40, 0x8c, 0xf9, + 0x39, 0xca, 0x3b, 0x85, 0xab, 0x0a, 0xc6, 0xfc, 0xbc, 0x63, 0xe2, 0x56, 0xe4, 0x9a, 0xcf, 0x48, + 0x17, 0xce, 0x68, 0xf8, 0x13, 0x8d, 0xb5, 0xc0, 0x72, 0xac, 0x51, 0x68, 0x3f, 0xb3, 0xc3, 0x0b, + 0xd2, 0x76, 0x33, 0x9a, 0x0c, 0xda, 0x2d, 0x40, 0xce, 0x3a, 0x9f, 0xf9, 0xea, 0x3e, 0x14, 0x45, + 0x29, 0x4b, 0x16, 0xdf, 0x35, 0x28, 0xd9, 0x01, 0xae, 0xc3, 0x20, 0x14, 0x9a, 0x5c, 0xd1, 0x0e, + 0x9a, 0x98, 0x44, 0x81, 0x6c, 0x1a, 0xa1, 0x21, 0x16, 0x2f, 0xfd, 0x56, 0x1f, 0x02, 0x24, 0xcd, + 0xc2, 0x3a, 0x39, 0x96, 0x1b, 0x29, 0x8d, 0x8e, 0xe5, 0xc6, 0x3c, 0xeb, 0x12, 0xcf, 0x35, 0x28, + 0xc7, 0x7a, 0x3a, 0xab, 0x42, 0xc6, 0x10, 0x9b, 0x71, 0xc6, 0x50, 0xef, 0xa1, 0xda, 0x1c, 0x69, + 0xe2, 0x69, 0x1c, 0xa6, 0xa2, 0x2d, 0x3a, 0x73, 0xac, 0x7e, 0x1f, 0xaa, 0x9a, 0x15, 0xcc, 0x9d, + 0xb0, 0xe9, 0x39, 0x2d, 0x6b, 0xcc, 0xde, 0x06, 0x88, 0xd3, 0x81, 0xd0, 0x99, 0x92, 0xb9, 0xdb, + 0xb2, 0xc6, 0x9a, 0x84, 0x57, 0xff, 0x5d, 0x8e, 0xb4, 0xcf, 0x16, 0x57, 0xfb, 0x84, 0x7e, 0x97, + 0x91, 0xf4, 0xbb, 0x78, 0xf7, 0x59, 0x4f, 0xeb, 0xb8, 0x27, 0xb6, 0x69, 0x5a, 0x6e, 0xa4, 0xcb, + 0xf2, 0x14, 0x0e, 0xb6, 0xe1, 0x4c, 0x84, 0xc0, 0x62, 0x51, 0xa1, 0xd3, 0x99, 0x6f, 0x05, 0x01, + 0xd7, 0xa2, 0x0c, 0x67, 0x12, 0xad, 0xed, 0xfc, 0x8b, 0xd6, 0xf6, 0x35, 0x28, 0xe1, 0x06, 0x4c, + 0x36, 0x68, 0x81, 0xf7, 0xbe, 0x30, 0xb6, 0xd9, 0x1b, 0x50, 0x14, 0xd6, 0x83, 0x58, 0x54, 0x62, + 0xba, 0xb4, 0x38, 0x50, 0x8b, 0xb0, 0xac, 0x8e, 0x2a, 0xe7, 0x74, 0x6a, 0xb9, 0x61, 0xa4, 0x35, + 0x88, 0x24, 0x7b, 0x0b, 0xca, 0x9e, 0xab, 0x73, 0x13, 0x43, 0xac, 0x2a, 0x31, 0x7d, 0xfb, 0xee, + 0x11, 0x41, 0xb5, 0x92, 0x27, 0x7e, 0x61, 0x55, 0x1c, 0xef, 0x4c, 0x1f, 0x19, 0xbe, 0x49, 0x2b, + 0xab, 0xa4, 0x15, 0x1d, 0xef, 0xac, 0x69, 0xf8, 0x26, 0xd7, 0xa2, 0xbe, 0x76, 0xe7, 0x53, 0x5a, + 0x4d, 0x35, 0x4d, 0xa4, 0xd8, 0x0d, 0x28, 0x8f, 0x9c, 0x79, 0x10, 0x5a, 0xfe, 0xee, 0x05, 0x37, + 0x1a, 0xb5, 0x04, 0x80, 0xf5, 0x9a, 0xf9, 0xf6, 0xd4, 0xf0, 0x2f, 0x68, 0xe9, 0x94, 0xb4, 0x28, + 0x49, 0x5b, 0xd9, 0xa9, 0x6d, 0x9e, 0x73, 0xcb, 0x51, 0xe3, 0x09, 0xa4, 0x3f, 0x21, 0xbb, 0x3e, + 0xa0, 0xf5, 0x51, 0xd2, 0xa2, 0x24, 0x8d, 0x03, 0xfd, 0xa4, 0x15, 0x51, 0xd6, 0x44, 0x2a, 0x65, + 0x02, 0x6c, 0x3d, 0xd7, 0x04, 0x60, 0x8b, 0x5a, 0x98, 0xe7, 0xdb, 0x13, 0x5b, 0xe8, 0x50, 0x97, + 0xb8, 0x16, 0xc6, 0x41, 0x44, 0xf0, 0x31, 0xd4, 0x26, 0x96, 0x6b, 0xf9, 0x46, 0x68, 0x99, 0x3a, + 0xca, 0xc5, 0x6d, 0xea, 0x38, 0x31, 0xcc, 0x8f, 0x23, 0x14, 0xca, 0x9a, 0xea, 0x44, 0x4a, 0xa9, + 0x5f, 0x43, 0x51, 0x8c, 0x0d, 0x6e, 0x8e, 0xb8, 0xee, 0xd2, 0x72, 0x9d, 0x6f, 0x8e, 0x08, 0x67, + 0xb7, 0xa1, 0x26, 0x2a, 0x11, 0x84, 0xbe, 0xed, 0x4e, 0xc4, 0xac, 0xab, 0x72, 0xe0, 0x80, 0x60, + 0xa8, 0xc3, 0xe0, 0xbc, 0xd0, 0x8d, 0x63, 0xdb, 0xc1, 0xf5, 0x9d, 0x15, 0xba, 0xd9, 0xdc, 0x71, + 0x1a, 0x1c, 0xa4, 0xf6, 0xa1, 0x14, 0x8d, 0xe4, 0xaf, 0xa4, 0x4c, 0x75, 0x06, 0x55, 0xb9, 0x85, + 0xbf, 0x9a, 0x86, 0x70, 0x1d, 0x25, 0x08, 0x3d, 0xdf, 0x32, 0x23, 0x97, 0x91, 0x1d, 0x0c, 0x28, + 0xad, 0xfe, 0x4e, 0x06, 0x2a, 0xa4, 0x2b, 0xf5, 0x49, 0x13, 0x64, 0x6f, 0x03, 0x1b, 0xf9, 0x96, + 0x11, 0x5a, 0xba, 0x75, 0x1e, 0xfa, 0x86, 0xd0, 0x88, 0xb8, 0x5a, 0xa5, 0x70, 0x4c, 0x1b, 0x11, + 0x5c, 0x29, 0xba, 0x05, 0x95, 0x99, 0xe1, 0x07, 0x91, 0x36, 0xce, 0x4b, 0x07, 0x0e, 0x12, 0xba, + 0xb0, 0xe2, 0x4e, 0x7c, 0x63, 0xaa, 0x87, 0xde, 0xa9, 0xe5, 0x72, 0x3b, 0x84, 0x5b, 0x60, 0x1b, + 0x04, 0x1f, 0x22, 0x98, 0xcc, 0x91, 0x7f, 0x95, 0x81, 0xda, 0x21, 0x9f, 0xa0, 0x9f, 0x5b, 0x17, + 0x2d, 0x6e, 0xf6, 0x8e, 0x22, 0xe1, 0x92, 0xd3, 0xe8, 0x37, 0xbb, 0x09, 0x95, 0xd9, 0xa9, 0x75, + 0xa1, 0xa7, 0x4c, 0xc4, 0x32, 0x82, 0x9a, 0x24, 0x46, 0xde, 0x84, 0x82, 0x47, 0x0d, 0x11, 0xdb, + 0xb1, 0xd8, 0xc5, 0xa4, 0x16, 0x6a, 0x82, 0x00, 0x75, 0xc7, 0x38, 0x2b, 0x59, 0x49, 0x15, 0x99, + 0x51, 0xf5, 0xb7, 0x21, 0x8f, 0xa8, 0xa0, 0x9e, 0xe7, 0x4a, 0x1f, 0x25, 0xd8, 0x7b, 0x50, 0x1b, + 0x79, 0xd3, 0x99, 0x1e, 0xb1, 0x8b, 0x8d, 0x39, 0x2d, 0xfe, 0x2a, 0x48, 0x72, 0xc8, 0xf3, 0x52, + 0x7f, 0x9e, 0x85, 0x12, 0xd5, 0x41, 0x48, 0x40, 0xdb, 0x3c, 0x8f, 0x24, 0x60, 0x59, 0xcb, 0xdb, + 0x26, 0x6e, 0x30, 0x2f, 0xd1, 0x53, 0x63, 0xfd, 0x33, 0x2b, 0xeb, 0x9f, 0x57, 0xa0, 0x20, 0x94, + 0xcf, 0x1c, 0x17, 0x91, 0xf3, 0xe7, 0xab, 0x9e, 0xf9, 0x55, 0xaa, 0x27, 0x0e, 0x21, 0xa7, 0xb1, + 0xce, 0x71, 0x2b, 0xe6, 0x52, 0x10, 0x08, 0xd4, 0x46, 0x88, 0x2c, 0xdf, 0x8a, 0x69, 0xf9, 0x56, + 0x87, 0xe2, 0x33, 0x3b, 0xb0, 0x71, 0x82, 0x94, 0xb8, 0xc4, 0x10, 0x49, 0x69, 0x18, 0xca, 0x2f, + 0x1b, 0x86, 0xb8, 0xd9, 0x86, 0x33, 0xe1, 0xf6, 0x52, 0xd4, 0xec, 0x86, 0x33, 0xf1, 0xd8, 0xfb, + 0x70, 0x39, 0x41, 0x8b, 0xd6, 0x90, 0xba, 0x4a, 0xae, 0x33, 0x8d, 0xc5, 0x94, 0xd4, 0x22, 0x32, + 0x68, 0xef, 0xc3, 0x96, 0xc4, 0x32, 0x43, 0x4d, 0x2c, 0x20, 0xf1, 0x58, 0xd6, 0x36, 0x63, 0x72, + 0x52, 0xd0, 0x02, 0xf6, 0x26, 0xf6, 0xd3, 0xc8, 0x99, 0x9b, 0x5c, 0xd8, 0xcc, 0xa7, 0x6e, 0x50, + 0xaf, 0x51, 0x07, 0x6f, 0x46, 0xf0, 0x26, 0x07, 0xab, 0x7f, 0x7f, 0x1d, 0x6a, 0x7b, 0x9e, 0x6f, + 0xd9, 0x13, 0x37, 0x99, 0xa0, 0x4b, 0x16, 0x53, 0x34, 0x69, 0xd7, 0xa5, 0x49, 0x7b, 0x0b, 0x2a, + 0x63, 0xce, 0xa8, 0x87, 0xc7, 0xdc, 0x31, 0x93, 0xd3, 0x40, 0x80, 0x86, 0xc7, 0x0e, 0x8a, 0x9a, + 0x88, 0x80, 0x98, 0x73, 0xc4, 0x1c, 0x31, 0xe1, 0x0e, 0xca, 0xbe, 0x47, 0x7b, 0x89, 0x69, 0x39, + 0x56, 0xc8, 0x47, 0x72, 0xe3, 0xe1, 0xab, 0x91, 0xfe, 0x22, 0xd5, 0xe9, 0x81, 0x66, 0x8d, 0x1b, + 0xa4, 0xf4, 0xe1, 0xd6, 0xd2, 0x22, 0x72, 0xc1, 0x2b, 0xf6, 0xa1, 0xc2, 0x37, 0xe4, 0xe5, 0x62, + 0x4d, 0x1d, 0x42, 0x39, 0x06, 0xa3, 0x06, 0xaf, 0xb5, 0x85, 0xd6, 0xbe, 0xc6, 0x2a, 0x50, 0x6c, + 0x36, 0x06, 0xcd, 0x46, 0xab, 0xad, 0x64, 0x10, 0x35, 0x68, 0x0f, 0xb9, 0xa6, 0xbe, 0xce, 0x36, + 0xa1, 0x82, 0xa9, 0x56, 0x7b, 0xaf, 0x71, 0xd4, 0x1d, 0x2a, 0x59, 0x56, 0x83, 0x72, 0xaf, 0xaf, + 0x37, 0x9a, 0xc3, 0x4e, 0xbf, 0xa7, 0xe4, 0xd4, 0x1f, 0x42, 0xa9, 0x79, 0x62, 0x8d, 0x4e, 0x9f, + 0xd7, 0x8b, 0xe4, 0xd8, 0xb0, 0x46, 0xa7, 0x42, 0xeb, 0x5e, 0x70, 0x6c, 0x58, 0xa3, 0x53, 0xb5, + 0x0d, 0xe5, 0x43, 0xc3, 0x0f, 0x6d, 0xaa, 0xd7, 0x27, 0x50, 0x8b, 0x13, 0x2d, 0x6b, 0x1c, 0xe9, + 0x23, 0x2c, 0xd6, 0xc5, 0x63, 0x94, 0x96, 0x26, 0x54, 0xdf, 0x86, 0xaa, 0x0c, 0x60, 0x37, 0x20, + 0x6b, 0x5a, 0xe3, 0x15, 0xf2, 0x16, 0xc1, 0xea, 0x13, 0xa8, 0x36, 0xa3, 0xfd, 0xf5, 0x79, 0x55, + 0x7f, 0x08, 0x1b, 0x24, 0x1c, 0x46, 0xc7, 0x91, 0x74, 0x58, 0x5f, 0x21, 0x1d, 0xaa, 0x48, 0xd3, + 0x3c, 0x16, 0xe2, 0xe1, 0x43, 0xa8, 0x1c, 0xfa, 0xde, 0xcc, 0xf2, 0x43, 0xca, 0x56, 0x81, 0xec, + 0xa9, 0x75, 0x21, 0x72, 0xc5, 0x9f, 0x89, 0xbf, 0x69, 0x5d, 0xf6, 0x37, 0x3d, 0x84, 0x52, 0xc4, + 0xf6, 0x8d, 0x79, 0x7e, 0x80, 0x52, 0x96, 0x78, 0x6c, 0x2b, 0xc0, 0xc2, 0x1e, 0x00, 0xcc, 0x62, + 0x80, 0xe8, 0xb8, 0xc8, 0x88, 0x11, 0x99, 0x6b, 0x12, 0x85, 0xfa, 0x2a, 0x14, 0x9f, 0xd8, 0xd6, + 0x99, 0x68, 0xfe, 0x33, 0xdb, 0x3a, 0x8b, 0x9a, 0x8f, 0xbf, 0xd5, 0xff, 0x56, 0x86, 0x12, 0x2d, + 0xc5, 0xd6, 0xf3, 0x5d, 0x7c, 0xdf, 0x46, 0xd7, 0xdb, 0x11, 0xeb, 0x29, 0xb7, 0x42, 0xc3, 0xe4, + 0xab, 0xeb, 0x55, 0x00, 0x49, 0x2c, 0x70, 0x21, 0x57, 0x0e, 0x63, 0x69, 0x80, 0x4a, 0x12, 0x6d, + 0x5b, 0xc1, 0xd7, 0x8e, 0xb0, 0xbe, 0x13, 0x00, 0x7b, 0xc0, 0x55, 0x18, 0xb2, 0xb7, 0xb9, 0x9a, + 0x77, 0x29, 0x32, 0x55, 0x8e, 0x1d, 0x2b, 0x32, 0xa0, 0x48, 0xaf, 0xc1, 0x04, 0x89, 0x3c, 0xcb, + 0x0f, 0x50, 0xb2, 0xd1, 0x19, 0x80, 0x16, 0x25, 0xd9, 0x1b, 0x90, 0xc3, 0xfd, 0x40, 0x18, 0x3c, + 0x97, 0xa2, 0x1e, 0x94, 0x36, 0x34, 0x8d, 0x08, 0xd8, 0x3d, 0x28, 0x92, 0x14, 0xb2, 0x50, 0x28, + 0x49, 0xbd, 0x1d, 0xed, 0x0f, 0x5a, 0x84, 0x66, 0x6f, 0x42, 0x7e, 0x7c, 0x6a, 0x5d, 0x70, 0x89, + 0x14, 0xe7, 0x99, 0x5a, 0xb3, 0x1a, 0xa7, 0x60, 0x77, 0x60, 0xc3, 0xb7, 0xc6, 0x3a, 0x39, 0xfd, + 0x50, 0xc8, 0x04, 0xf5, 0x0d, 0x92, 0x21, 0x55, 0xdf, 0x1a, 0x37, 0x11, 0x38, 0x3c, 0x76, 0x02, + 0x76, 0x17, 0x0a, 0xb4, 0x7a, 0x50, 0xc3, 0x93, 0x4a, 0x8e, 0x96, 0xa2, 0x26, 0xb0, 0xec, 0x7d, + 0x00, 0xa1, 0x47, 0xea, 0xc7, 0x17, 0xe4, 0x2c, 0x8f, 0x17, 0x93, 0x3c, 0xff, 0x65, 0x6d, 0xf3, + 0x0d, 0xc8, 0xe3, 0x24, 0x09, 0xea, 0x57, 0x29, 0xe7, 0xad, 0xf4, 0x0c, 0xa2, 0x9a, 0x12, 0x9e, + 0xdd, 0x83, 0x12, 0x4e, 0x14, 0x1d, 0x87, 0xa3, 0x2e, 0x2b, 0xd6, 0x62, 0x56, 0xe1, 0x26, 0x62, + 0x9d, 0x0d, 0xbe, 0x76, 0xd8, 0x7d, 0xc8, 0x99, 0xb8, 0x98, 0xaf, 0x51, 0x8e, 0x57, 0xa4, 0x71, + 0x41, 0x61, 0xd5, 0xb2, 0xc6, 0xa4, 0xeb, 0x13, 0x0d, 0xdb, 0x87, 0x0d, 0x9c, 0x46, 0x0f, 0x49, + 0x2f, 0xc0, 0xee, 0xab, 0x5f, 0x27, 0xae, 0xd7, 0x16, 0xb8, 0x7a, 0x82, 0x88, 0x3a, 0xbb, 0xed, + 0x86, 0xfe, 0x85, 0x56, 0x73, 0x65, 0x18, 0xbb, 0x8e, 0x06, 0x59, 0xd7, 0x1b, 0x9d, 0x5a, 0x66, + 0xfd, 0x95, 0x48, 0x59, 0xe2, 0x69, 0xf6, 0x29, 0xd4, 0x68, 0x62, 0x61, 0x12, 0x0b, 0xaf, 0xdf, + 0x20, 0x61, 0x2a, 0x4f, 0x99, 0x08, 0xa5, 0xa5, 0x29, 0x51, 0xc4, 0xdb, 0x81, 0x1e, 0x5a, 0xd3, + 0x99, 0xe7, 0xa3, 0x4a, 0xfe, 0x6a, 0xe4, 0xa8, 0x1a, 0x46, 0x20, 0xdc, 0xb3, 0xe3, 0xa3, 0x3d, + 0xdd, 0x1b, 0x8f, 0x03, 0x2b, 0xac, 0xdf, 0xa4, 0x75, 0xb3, 0x11, 0x9d, 0xf0, 0xf5, 0x09, 0x4a, + 0x7b, 0x66, 0xa0, 0x9b, 0x17, 0xae, 0x31, 0xb5, 0x47, 0xf5, 0x5b, 0x5c, 0xf3, 0xb7, 0x83, 0x16, + 0x07, 0xc8, 0xca, 0xf7, 0x4e, 0x4a, 0xf9, 0xbe, 0x04, 0x79, 0xf3, 0x18, 0x97, 0xe3, 0x6b, 0x94, + 0x6d, 0xce, 0x3c, 0xee, 0x98, 0xec, 0x1d, 0x28, 0xcf, 0x22, 0x11, 0x58, 0x57, 0x65, 0x17, 0x43, + 0x2c, 0x19, 0xb5, 0x84, 0x02, 0xad, 0xde, 0x3d, 0xcb, 0x08, 0xe7, 0xbe, 0xb5, 0xe7, 0x18, 0x93, + 0xfa, 0x6d, 0xca, 0x49, 0x06, 0x61, 0xed, 0x1c, 0x6f, 0x62, 0x8f, 0x0c, 0x5a, 0xf9, 0x77, 0xb8, + 0x8a, 0x26, 0x20, 0x1d, 0x33, 0xd1, 0x59, 0x0d, 0xa1, 0x77, 0xbd, 0x2e, 0xeb, 0xac, 0x86, 0x13, + 0x79, 0xe5, 0x92, 0xbe, 0xb0, 0x66, 0xde, 0xe8, 0xa4, 0x7e, 0x97, 0xd6, 0x5b, 0x2d, 0xea, 0x8a, + 0x36, 0x02, 0xaf, 0x3f, 0x26, 0xa5, 0x9f, 0x7a, 0xf8, 0xc3, 0x05, 0x41, 0x96, 0x5a, 0x86, 0x92, + 0xc4, 0xdb, 0x5f, 0x93, 0xe5, 0xd9, 0x6e, 0x9e, 0x24, 0xfe, 0xf5, 0x1f, 0x02, 0x5b, 0x9e, 0x1b, + 0x2f, 0x93, 0xaa, 0x79, 0x21, 0x55, 0xbf, 0xb7, 0xfe, 0x49, 0x46, 0x7d, 0x02, 0xb5, 0x94, 0xd0, + 0x58, 0xb9, 0x3b, 0x70, 0x2d, 0xce, 0x98, 0x0a, 0x03, 0x9d, 0x27, 0x22, 0x0d, 0xdd, 0x76, 0x27, + 0xc2, 0xfb, 0xc8, 0x35, 0x74, 0x4a, 0xab, 0xff, 0x38, 0x0b, 0xd5, 0x7d, 0x23, 0x38, 0x39, 0x30, + 0x66, 0x83, 0xd0, 0x08, 0x03, 0x9c, 0x4a, 0x27, 0x46, 0x70, 0x32, 0x35, 0x66, 0x5c, 0x9f, 0xce, + 0x70, 0xc7, 0x83, 0x80, 0xa1, 0x32, 0x8d, 0x93, 0x18, 0x93, 0x7d, 0xf7, 0xf0, 0x73, 0xe1, 0x55, + 0x88, 0xd3, 0x28, 0xc2, 0x82, 0x93, 0xf9, 0x78, 0x1c, 0x17, 0x15, 0x25, 0xd9, 0x1d, 0xa8, 0x89, + 0x9f, 0xa4, 0x4c, 0x9f, 0x8b, 0x63, 0xe4, 0x34, 0x90, 0x3d, 0x82, 0x8a, 0x00, 0x0c, 0x23, 0x81, + 0xbb, 0x11, 0x7b, 0x8b, 0x12, 0x84, 0x26, 0x53, 0xb1, 0x1f, 0xc1, 0x65, 0x29, 0xb9, 0xe7, 0xf9, + 0x07, 0x73, 0x27, 0xb4, 0x9b, 0x3d, 0xa1, 0x8e, 0xbc, 0xb2, 0xc4, 0x9e, 0x90, 0x68, 0xab, 0x39, + 0xd3, 0xb5, 0x3d, 0xb0, 0x5d, 0x92, 0xdf, 0x59, 0x2d, 0x0d, 0x5c, 0xa0, 0x32, 0xce, 0x49, 0x6c, + 0xa7, 0xa9, 0x8c, 0x73, 0x5c, 0xd8, 0x02, 0x70, 0x60, 0x85, 0x27, 0x9e, 0x49, 0x6a, 0x6b, 0xbc, + 0xb0, 0x07, 0x32, 0x4a, 0x4b, 0x53, 0x62, 0x77, 0xa2, 0x49, 0x38, 0x72, 0x43, 0x52, 0x5e, 0xb3, + 0x5a, 0x94, 0xc4, 0x2d, 0xcd, 0x37, 0xdc, 0x89, 0x15, 0xd4, 0x2b, 0x3b, 0xd9, 0x7b, 0x19, 0x4d, + 0xa4, 0xd4, 0xbf, 0xb2, 0x0e, 0x79, 0x3e, 0x92, 0xaf, 0x40, 0xf9, 0x98, 0x9c, 0xff, 0x68, 0xdb, + 0x0b, 0x27, 0x3d, 0x01, 0x7a, 0xf3, 0x29, 0xd7, 0x24, 0x85, 0x57, 0x28, 0xa3, 0xd1, 0x6f, 0xcc, + 0xd2, 0x9b, 0x87, 0x58, 0x56, 0x96, 0xa0, 0x22, 0x85, 0x95, 0xf0, 0xbd, 0x33, 0x9a, 0x0d, 0x39, + 0x42, 0x44, 0x49, 0x3a, 0x07, 0xa0, 0xdd, 0x11, 0x99, 0xf2, 0x84, 0x2b, 0x11, 0xa0, 0xe9, 0x86, + 0x8b, 0x1e, 0xac, 0xc2, 0x92, 0x07, 0x8b, 0xdd, 0x04, 0xd4, 0x53, 0x47, 0x56, 0xdf, 0xb5, 0x9a, + 0x3d, 0xea, 0xe1, 0x92, 0x26, 0x41, 0x70, 0x81, 0x98, 0xde, 0x8c, 0x3a, 0x35, 0xaf, 0xe1, 0x4f, + 0xf6, 0x51, 0x3c, 0x3b, 0xa9, 0x8d, 0xc2, 0x00, 0x10, 0xbb, 0x87, 0x3c, 0x8f, 0xb5, 0x14, 0x1d, + 0xe6, 0x84, 0x5b, 0x02, 0x37, 0x00, 0xf0, 0xa7, 0xda, 0x06, 0xd0, 0xbc, 0xb3, 0xc0, 0x0a, 0xc9, + 0x55, 0x7b, 0x95, 0x9a, 0x98, 0x3a, 0xf2, 0xf3, 0xce, 0x0e, 0xbd, 0x20, 0xb6, 0x91, 0xd7, 0x57, + 0xdb, 0xc8, 0xea, 0xbb, 0x50, 0x44, 0x7d, 0xc1, 0x08, 0x0d, 0x76, 0x47, 0x78, 0xc7, 0xb8, 0x96, + 0x23, 0xdc, 0x84, 0x49, 0x19, 0xc2, 0x5f, 0xd6, 0x8d, 0xca, 0x25, 0x9e, 0xd7, 0x24, 0x2b, 0x34, + 0xde, 0xab, 0x44, 0x86, 0x42, 0x03, 0x79, 0x05, 0xca, 0x58, 0x35, 0x3a, 0xdb, 0x10, 0x72, 0xa1, + 0xe4, 0x7b, 0x67, 0x4d, 0x4c, 0xab, 0xff, 0x3a, 0x03, 0x95, 0xbe, 0x6f, 0xe2, 0x26, 0x39, 0x98, + 0x59, 0xa3, 0x97, 0x9a, 0xf4, 0xa8, 0xaf, 0x78, 0x8e, 0x63, 0x90, 0x38, 0x16, 0x56, 0x60, 0x0c, + 0x60, 0xef, 0x43, 0x6e, 0x8c, 0x62, 0x37, 0x2b, 0x6b, 0xf1, 0x52, 0xf6, 0xd1, 0x6f, 0x14, 0xc4, + 0x1a, 0x91, 0xaa, 0xbf, 0x19, 0x97, 0x4f, 0xd2, 0x59, 0xf6, 0xcf, 0xaf, 0xd1, 0xb9, 0xdd, 0xa0, + 0xa9, 0x64, 0x58, 0x09, 0x72, 0xad, 0xf6, 0xa0, 0xc9, 0x75, 0x77, 0xd4, 0xe2, 0x07, 0xfa, 0x5e, + 0x47, 0x1b, 0x0c, 0x95, 0x1c, 0x1d, 0x04, 0x12, 0xa0, 0xdb, 0x18, 0x0c, 0x95, 0x12, 0x03, 0x28, + 0x1c, 0xf5, 0x3a, 0x3f, 0x3a, 0x6a, 0x2b, 0x8a, 0xfa, 0xcf, 0x33, 0x00, 0x89, 0x1b, 0x99, 0xbd, + 0x05, 0x95, 0x33, 0x4a, 0xe9, 0xd2, 0x09, 0x86, 0xdc, 0x46, 0xe0, 0x68, 0xd2, 0xa5, 0xde, 0x81, + 0x6a, 0xbc, 0xad, 0xa0, 0x9e, 0xb1, 0x7c, 0x94, 0x51, 0x89, 0xf1, 0xbb, 0x17, 0xec, 0x6d, 0x28, + 0x79, 0xd8, 0x0e, 0x24, 0xcd, 0xca, 0x4a, 0x86, 0xd4, 0x7c, 0xad, 0xe8, 0xf1, 0x04, 0xea, 0x23, + 0x63, 0x3f, 0xb2, 0xea, 0x63, 0xd2, 0x3d, 0x04, 0x35, 0x1d, 0x63, 0x1e, 0x58, 0x1a, 0xc7, 0xc7, + 0x52, 0x3a, 0x9f, 0x48, 0x69, 0xf5, 0xc7, 0xb0, 0x31, 0x30, 0xa6, 0x33, 0x2e, 0xcb, 0xa9, 0x61, + 0x0c, 0x72, 0x38, 0x27, 0xc4, 0xd4, 0xa3, 0xdf, 0xb8, 0xe8, 0x0e, 0x2d, 0x7f, 0x64, 0xb9, 0xd1, + 0x1a, 0x8d, 0x92, 0x28, 0x7e, 0x8f, 0x50, 0x9a, 0x6b, 0xde, 0x59, 0x24, 0xce, 0xa3, 0xb4, 0xfa, + 0xd7, 0x33, 0x50, 0x91, 0xaa, 0xc1, 0xde, 0x85, 0x1c, 0x29, 0xae, 0x19, 0x59, 0x10, 0x4a, 0x04, + 0xfc, 0x37, 0x57, 0x75, 0x90, 0x90, 0xdd, 0x85, 0x7c, 0x10, 0x1a, 0x7e, 0x74, 0x22, 0xa1, 0x48, + 0x1c, 0xbb, 0xde, 0xdc, 0x35, 0x35, 0x8e, 0x66, 0x2a, 0x64, 0x2d, 0xd7, 0x14, 0x7e, 0x90, 0x65, + 0x2a, 0x44, 0xaa, 0x3b, 0x50, 0x8e, 0xb3, 0xc7, 0x29, 0xa0, 0xf5, 0x9f, 0x0e, 0x94, 0x35, 0x56, + 0x86, 0xbc, 0xd6, 0xe8, 0x3d, 0x6e, 0x2b, 0x19, 0xf5, 0x67, 0x19, 0x80, 0x84, 0x8b, 0x3d, 0x48, + 0xd5, 0xf6, 0xfa, 0x62, 0xae, 0x0f, 0xe8, 0xaf, 0x54, 0xd9, 0x1b, 0x50, 0x9e, 0xbb, 0x04, 0xb4, + 0x4c, 0xb1, 0x13, 0x25, 0x00, 0xb4, 0xb6, 0xa2, 0x68, 0x9e, 0x05, 0x6b, 0xeb, 0x99, 0xe1, 0xa8, + 0xdf, 0x83, 0x72, 0x9c, 0x1d, 0x1a, 0x90, 0x7b, 0xfd, 0x6e, 0xb7, 0xff, 0xb4, 0xd3, 0x7b, 0xac, + 0xac, 0x61, 0xf2, 0x50, 0x6b, 0x37, 0xdb, 0x2d, 0x4c, 0x66, 0x70, 0xce, 0x36, 0x8f, 0x34, 0xad, + 0xdd, 0x1b, 0xea, 0x5a, 0xff, 0xa9, 0xb2, 0xae, 0xfe, 0x6e, 0x06, 0x6a, 0x7d, 0x07, 0x6d, 0xf7, + 0xa6, 0x31, 0x43, 0xcd, 0x84, 0xfd, 0x00, 0xb6, 0x8e, 0xe7, 0xa8, 0x1d, 0xcf, 0x1c, 0x63, 0x64, + 0x9d, 0x78, 0x8e, 0x69, 0x45, 0x8b, 0x30, 0x75, 0xe6, 0x22, 0xdc, 0xc3, 0x0a, 0x11, 0x1f, 0x26, + 0xb4, 0xec, 0x43, 0xa8, 0xce, 0x7c, 0xef, 0xd8, 0xd2, 0x03, 0x6f, 0xee, 0x8f, 0xac, 0x25, 0xb3, + 0x2e, 0xe1, 0xad, 0x10, 0xdd, 0x80, 0xc8, 0xd4, 0xbf, 0xb9, 0x0e, 0xd5, 0x96, 0x65, 0xce, 0x67, + 0x9f, 0x79, 0xb6, 0xdb, 0x0c, 0xcf, 0xd9, 0x07, 0x50, 0xf5, 0x1c, 0xf2, 0x36, 0xe8, 0x52, 0xbc, + 0xc1, 0xaa, 0x7c, 0xc0, 0xa3, 0x16, 0x50, 0x74, 0xc2, 0x3b, 0x70, 0x89, 0x1b, 0xf0, 0xc2, 0xf5, + 0x75, 0xce, 0x99, 0x71, 0xcd, 0xe4, 0x35, 0x85, 0xa3, 0xf8, 0x06, 0x4d, 0xe4, 0xbf, 0x06, 0xdb, + 0x12, 0x39, 0x0a, 0x16, 0x4e, 0x9f, 0x5d, 0x5a, 0x63, 0x5b, 0x31, 0x6f, 0x1c, 0x09, 0xf1, 0x19, + 0x6c, 0x47, 0x35, 0x1c, 0xf1, 0xde, 0xe3, 0xcc, 0x39, 0xd9, 0x0c, 0x49, 0xf5, 0xae, 0xa8, 0xf0, + 0x96, 0x27, 0x03, 0x29, 0xaf, 0xf7, 0xe1, 0xb2, 0x89, 0xad, 0xd7, 0x79, 0xe7, 0x9f, 0x5a, 0xd6, + 0x4c, 0x77, 0x8c, 0x20, 0x14, 0x87, 0xa5, 0x8c, 0x90, 0xbb, 0x88, 0xfb, 0xdc, 0xb2, 0x66, 0x5d, + 0x23, 0x08, 0xd5, 0x7f, 0x9a, 0x85, 0x32, 0xf7, 0x3e, 0x60, 0x77, 0xdd, 0x83, 0xa2, 0x77, 0xfc, + 0x95, 0xee, 0xc7, 0x56, 0xf9, 0xd2, 0x81, 0x68, 0xc1, 0x3b, 0xfe, 0x4a, 0xb3, 0xc6, 0xec, 0xad, + 0x68, 0xab, 0x43, 0x0b, 0x7e, 0x5d, 0x76, 0xb7, 0x47, 0xea, 0xbf, 0xd8, 0xfa, 0xd0, 0x34, 0x7d, + 0x04, 0x15, 0xdb, 0x0d, 0x2c, 0x3f, 0xe4, 0x1e, 0x97, 0xe2, 0xf3, 0x07, 0x81, 0x93, 0x91, 0x13, + 0xe6, 0x11, 0x54, 0xb8, 0x07, 0x86, 0x33, 0x95, 0x9e, 0xcf, 0xc4, 0xc9, 0x88, 0xe9, 0x53, 0xd8, + 0x48, 0xc4, 0x1c, 0xf1, 0x95, 0x9f, 0xcb, 0x57, 0x8b, 0x29, 0x89, 0xf5, 0x21, 0x5c, 0x09, 0x4e, + 0xed, 0x99, 0x2e, 0x6a, 0xea, 0xb9, 0x74, 0x52, 0xa1, 0xcf, 0x4e, 0xc5, 0x09, 0x01, 0x43, 0x6c, + 0x87, 0x90, 0x7d, 0xb7, 0x37, 0x77, 0x9c, 0xc3, 0x53, 0xf6, 0x26, 0x6c, 0x09, 0xf2, 0xd9, 0x69, + 0x34, 0x57, 0xc8, 0x28, 0xcd, 0x6b, 0x1b, 0x1c, 0x71, 0x78, 0x2a, 0x34, 0xb9, 0x8f, 0xa1, 0xce, + 0xcf, 0xe8, 0x45, 0xa3, 0x8c, 0xf1, 0xd8, 0x1a, 0x85, 0x3a, 0xaa, 0x0e, 0xe2, 0x38, 0xe1, 0x32, + 0xe1, 0xb9, 0x1b, 0xa9, 0x41, 0x58, 0x92, 0x7c, 0xef, 0xc1, 0xb6, 0x3d, 0x71, 0x3d, 0x3f, 0x62, + 0xb1, 0x4c, 0xce, 0xc4, 0xcf, 0x19, 0x18, 0xc7, 0x35, 0x04, 0x0a, 0x39, 0xd4, 0xbf, 0x95, 0x81, + 0x32, 0xaf, 0x28, 0x8e, 0xe9, 0x6b, 0x90, 0x7d, 0xc1, 0x78, 0x22, 0x8e, 0xdd, 0x87, 0x2d, 0xc3, + 0x34, 0x17, 0xf2, 0xe7, 0x02, 0x64, 0xd3, 0x30, 0x4d, 0x39, 0x73, 0x72, 0x76, 0x06, 0x7a, 0x64, + 0xb1, 0x26, 0x67, 0xf8, 0x25, 0x6d, 0xc3, 0x0e, 0x84, 0xc1, 0xca, 0xfd, 0xd5, 0xa9, 0x29, 0x92, + 0x7b, 0xf1, 0x14, 0x51, 0x19, 0x80, 0x66, 0x91, 0xb4, 0x68, 0x86, 0xe7, 0x9f, 0xe5, 0x4a, 0x19, + 0x05, 0xd4, 0x7f, 0x50, 0x81, 0x4a, 0xc3, 0x35, 0x9c, 0x8b, 0x9f, 0x58, 0x14, 0x80, 0x40, 0xde, + 0xcb, 0xd9, 0x5c, 0x74, 0x1a, 0x3f, 0x3b, 0x2b, 0x13, 0x84, 0x6a, 0x76, 0x0b, 0x2a, 0xde, 0x3c, + 0x8c, 0xf1, 0xfc, 0x34, 0x0d, 0x38, 0x88, 0x08, 0x62, 0xfe, 0xd8, 0x33, 0x1e, 0xf1, 0x93, 0x1e, + 0x9f, 0xf0, 0xc7, 0xba, 0x5d, 0xcc, 0x4f, 0x04, 0xb7, 0xa1, 0x16, 0xda, 0x53, 0x8b, 0x0e, 0x10, + 0xe7, 0x53, 0xcb, 0xe4, 0x51, 0x8e, 0x3c, 0x70, 0xae, 0x29, 0x60, 0x98, 0xcb, 0xd4, 0x9a, 0x7a, + 0xfe, 0x05, 0xcf, 0xa5, 0xc0, 0x73, 0xe1, 0x20, 0xca, 0xe5, 0x6d, 0x60, 0x67, 0x86, 0x1d, 0xea, + 0xe9, 0xac, 0xb8, 0x3e, 0xad, 0x20, 0x66, 0x28, 0x67, 0x77, 0x05, 0x0a, 0xa6, 0x1d, 0x9c, 0x76, + 0xfa, 0x42, 0x97, 0x16, 0x29, 0x6c, 0x4b, 0x30, 0x32, 0x70, 0x2b, 0x0f, 0x2d, 0xae, 0xf7, 0x65, + 0xb5, 0x32, 0x42, 0x76, 0x11, 0x80, 0x5b, 0x81, 0x6b, 0x85, 0x67, 0x9e, 0x8f, 0x9c, 0x5c, 0x55, + 0x4e, 0x00, 0xb8, 0x65, 0x22, 0x29, 0x16, 0x44, 0xb3, 0x35, 0xab, 0xc5, 0x69, 0x54, 0x42, 0xf9, + 0xcc, 0x25, 0x6c, 0x95, 0x57, 0x3f, 0x81, 0xb0, 0x3b, 0xb0, 0x41, 0xd5, 0x27, 0x55, 0x1a, 0xdb, + 0x40, 0x13, 0x31, 0xab, 0x55, 0x11, 0x4a, 0x16, 0x38, 0x52, 0x7d, 0x0a, 0xd7, 0x52, 0xed, 0xd3, + 0x0d, 0xdf, 0x37, 0x2e, 0xf4, 0xa9, 0xf1, 0x95, 0xe7, 0x93, 0xb7, 0x24, 0xab, 0x5d, 0x91, 0xbb, + 0xad, 0x81, 0xe8, 0x03, 0xc4, 0x3e, 0x97, 0xd5, 0x76, 0x3d, 0x9f, 0x5c, 0x29, 0x2b, 0x59, 0x11, + 0x4b, 0x76, 0x3f, 0x0d, 0x30, 0xe9, 0xf5, 0x01, 0x0f, 0xb8, 0xd4, 0x2a, 0x04, 0xdb, 0x25, 0x10, + 0x6a, 0xb6, 0xc1, 0x23, 0x2e, 0x61, 0xb7, 0x44, 0xf4, 0xd3, 0x23, 0x92, 0x9d, 0x1c, 0x71, 0x62, + 0x19, 0x26, 0x1d, 0xa2, 0x11, 0x62, 0xdf, 0x32, 0xe8, 0x88, 0x3a, 0x78, 0xa4, 0xcf, 0xe6, 0x21, + 0x8f, 0x94, 0xd4, 0xf2, 0xc1, 0xa3, 0xc3, 0x79, 0x28, 0xc0, 0x13, 0x2b, 0xa4, 0x33, 0x33, 0x02, + 0x3f, 0xb6, 0x42, 0x54, 0x4f, 0x83, 0x47, 0x91, 0xeb, 0xf8, 0xb2, 0xe8, 0xdb, 0x47, 0xc2, 0x37, + 0xac, 0x42, 0x2d, 0x46, 0xea, 0xd3, 0x39, 0x0f, 0x8d, 0xcc, 0x6a, 0x95, 0x88, 0xe0, 0x60, 0xee, + 0xe0, 0xc0, 0x8e, 0x8c, 0xd1, 0x89, 0xa5, 0xfb, 0x58, 0x95, 0xab, 0x7c, 0xe8, 0x08, 0xa2, 0x61, + 0x6d, 0x5e, 0x01, 0x9e, 0xd0, 0x4f, 0xec, 0x90, 0x5c, 0x3a, 0x59, 0xad, 0x44, 0x80, 0x7d, 0x3b, + 0xc4, 0x75, 0xcc, 0x91, 0x62, 0x06, 0x52, 0x16, 0xd7, 0x88, 0x68, 0x93, 0x10, 0x07, 0x04, 0xa7, + 0x8c, 0xee, 0x81, 0x92, 0xa2, 0xc5, 0xfc, 0xae, 0x13, 0xe9, 0x86, 0x44, 0x8a, 0xb9, 0xde, 0x05, + 0xce, 0xac, 0xe3, 0xd4, 0xe3, 0x79, 0xbe, 0xc2, 0xed, 0x3a, 0x02, 0xb7, 0xec, 0xe0, 0x94, 0x72, + 0xbc, 0x03, 0x1b, 0x12, 0x1d, 0xe6, 0x77, 0x83, 0xcf, 0x8c, 0x98, 0x2c, 0x55, 0x47, 0xdf, 0x9a, + 0x7a, 0xa1, 0x68, 0xe6, 0xab, 0x52, 0x1d, 0x35, 0x82, 0xa7, 0xeb, 0x28, 0x68, 0x31, 0xcf, 0x9b, + 0x52, 0x1d, 0x39, 0x29, 0xe6, 0xfa, 0x1a, 0x54, 0xcf, 0x7c, 0x3b, 0x0c, 0x2d, 0x97, 0x2f, 0xfe, + 0x5b, 0xbc, 0x63, 0x05, 0x8c, 0x56, 0xff, 0x6b, 0x50, 0xe5, 0x3d, 0x2f, 0xe4, 0xdb, 0x0e, 0x27, + 0x11, 0xb0, 0x48, 0x40, 0x88, 0xde, 0x98, 0xda, 0x2e, 0xf9, 0x6d, 0xb2, 0x5a, 0x99, 0x43, 0xd0, + 0xbc, 0x95, 0xd0, 0xc6, 0x39, 0x79, 0x6f, 0x12, 0xb4, 0x71, 0x4e, 0x4b, 0x72, 0x66, 0x3b, 0x0e, + 0x5f, 0xf8, 0xb7, 0xc5, 0x92, 0x44, 0x08, 0xad, 0xfb, 0x18, 0x4d, 0xa5, 0xdf, 0x91, 0xd0, 0x54, + 0x36, 0x4e, 0x1c, 0x42, 0x63, 0xd1, 0xaf, 0x8b, 0x89, 0x83, 0x00, 0x2c, 0x39, 0x41, 0x1a, 0xe7, + 0xe4, 0x9b, 0x89, 0x91, 0xc6, 0xb9, 0x90, 0x5b, 0x98, 0x2b, 0xf1, 0xbe, 0x11, 0xcb, 0x2d, 0x04, + 0x21, 0xb7, 0x4c, 0x60, 0x9c, 0xd7, 0xef, 0xa5, 0x09, 0x8c, 0x73, 0xb2, 0xa9, 0x2c, 0xc3, 0xe4, + 0x15, 0x7f, 0x93, 0x67, 0x8f, 0x00, 0xaa, 0xf7, 0x0e, 0x54, 0x83, 0x47, 0x7a, 0x82, 0xbf, 0xcf, + 0xd9, 0x83, 0x47, 0x5a, 0x44, 0x71, 0x07, 0x36, 0xe2, 0xa9, 0xc1, 0x69, 0xde, 0xe2, 0x03, 0x6f, + 0x8a, 0xa9, 0x41, 0x67, 0x8e, 0x3f, 0xcd, 0xc0, 0xf5, 0x3e, 0xb9, 0x9d, 0x48, 0xfc, 0x1f, 0x58, + 0x41, 0x60, 0x4c, 0xac, 0x3d, 0xcf, 0xdf, 0x9b, 0xff, 0xe4, 0x27, 0x17, 0xec, 0x1e, 0x6c, 0x1e, + 0x1a, 0xbe, 0xe5, 0x86, 0xf1, 0x69, 0x99, 0xf0, 0xe5, 0x2c, 0x82, 0xd9, 0x27, 0xa0, 0x70, 0x10, + 0x8f, 0xfc, 0x6a, 0x46, 0x27, 0x40, 0x8b, 0x1e, 0xeb, 0x25, 0x2a, 0x34, 0x0f, 0xcb, 0x2d, 0x3b, + 0x08, 0x35, 0xc3, 0x9d, 0xa0, 0x8c, 0x52, 0x1c, 0xef, 0x0c, 0x6d, 0x1c, 0x54, 0x7c, 0x75, 0x49, + 0xd5, 0x16, 0xbb, 0x64, 0xa2, 0x5f, 0x6f, 0x10, 0x61, 0xa2, 0x20, 0x7f, 0x0a, 0xca, 0x7c, 0x36, + 0x4b, 0xb3, 0xae, 0x3f, 0x87, 0x95, 0x08, 0x13, 0xd6, 0xb7, 0xa0, 0x22, 0x95, 0xba, 0x42, 0x1d, + 0x87, 0xa4, 0x2c, 0x24, 0x96, 0xca, 0x59, 0x11, 0x79, 0x0a, 0x49, 0xee, 0xea, 0xff, 0xbd, 0x0e, + 0x0a, 0xb9, 0xd3, 0x34, 0x8a, 0x18, 0xa0, 0x43, 0xb7, 0x94, 0x21, 0x97, 0x79, 0xa9, 0x21, 0xb7, + 0x03, 0x79, 0xc7, 0x9e, 0xc6, 0x01, 0x58, 0xa9, 0xa3, 0x20, 0x42, 0xe0, 0x58, 0x7b, 0xbe, 0x3d, + 0x21, 0x93, 0x53, 0x0e, 0x46, 0x24, 0x8f, 0x22, 0x5a, 0x70, 0x34, 0x44, 0x0f, 0x00, 0x4c, 0x3b, + 0x08, 0x75, 0x72, 0xc2, 0x88, 0x6a, 0x8b, 0x9e, 0x89, 0xfb, 0x5f, 0x2b, 0x9b, 0xf1, 0x50, 0xdc, + 0x03, 0x45, 0x52, 0xdb, 0xdc, 0xd8, 0x7f, 0x92, 0xd7, 0x12, 0x75, 0xae, 0xe9, 0x36, 0xdd, 0x70, + 0x89, 0x12, 0x15, 0xae, 0xc2, 0x12, 0x65, 0xc7, 0x3c, 0x57, 0xff, 0x54, 0x85, 0x5c, 0xcf, 0x33, + 0x2d, 0xf6, 0x1e, 0x94, 0x29, 0xc2, 0x57, 0x1a, 0x60, 0xa1, 0x56, 0x23, 0x9a, 0xfe, 0xd0, 0x48, + 0x95, 0x5c, 0xf1, 0xeb, 0xf9, 0x31, 0xc1, 0xaf, 0x91, 0x39, 0x48, 0xe7, 0xc2, 0xd8, 0xa4, 0x8a, + 0x70, 0x59, 0x91, 0x87, 0x85, 0x63, 0x70, 0x6f, 0xa5, 0x83, 0x01, 0xdf, 0x72, 0x49, 0x87, 0xcf, + 0x6b, 0x71, 0x9a, 0x8c, 0x70, 0xdf, 0x43, 0xd5, 0x8b, 0xef, 0x40, 0xf9, 0x15, 0x46, 0x38, 0xc7, + 0xd3, 0x96, 0xf4, 0x1e, 0x94, 0xbf, 0xf2, 0x6c, 0x97, 0x57, 0xbc, 0xb0, 0x54, 0x71, 0xb4, 0x71, + 0x78, 0xc5, 0xbf, 0x12, 0xbf, 0xd8, 0x6d, 0x28, 0x7a, 0x2e, 0xcf, 0xbb, 0xb8, 0x94, 0x77, 0xc1, + 0x73, 0xbb, 0x3c, 0xc4, 0xab, 0x66, 0x07, 0xba, 0x6f, 0x4f, 0x4e, 0x42, 0x1d, 0x39, 0xc5, 0x79, + 0x72, 0xc5, 0x0e, 0x34, 0x84, 0x61, 0xb6, 0x38, 0xf1, 0xc6, 0xb6, 0x83, 0x1a, 0x1e, 0x65, 0x56, + 0x5e, 0xca, 0x0c, 0x38, 0x9a, 0x32, 0x7c, 0x1d, 0x4a, 0x13, 0xdf, 0x43, 0xb3, 0xe3, 0xa2, 0x0e, + 0x4b, 0x94, 0x45, 0xc2, 0xed, 0x5e, 0xa0, 0xfa, 0x44, 0x3f, 0x6d, 0x77, 0xa2, 0x93, 0x5f, 0xa5, + 0xb2, 0x93, 0xbd, 0x57, 0xd2, 0xaa, 0x11, 0x90, 0x3c, 0x26, 0xaf, 0x43, 0xc9, 0x98, 0x4c, 0x74, + 0x11, 0xa9, 0xb6, 0x94, 0x97, 0x31, 0x99, 0x50, 0x91, 0x0f, 0xa0, 0x76, 0x66, 0xbb, 0x7a, 0x30, + 0xb3, 0x46, 0x9c, 0xb6, 0xb6, 0xdc, 0x95, 0x67, 0xb6, 0x8b, 0xb3, 0x9b, 0xe8, 0xe5, 0x65, 0xb0, + 0xf1, 0xcd, 0x97, 0xc1, 0xe6, 0xf3, 0x96, 0x81, 0x0a, 0x05, 0x71, 0x68, 0xa0, 0x2c, 0x91, 0x08, + 0x0c, 0x7b, 0x1f, 0x2a, 0xbe, 0xe1, 0x9e, 0xea, 0xe2, 0x70, 0xfe, 0x4b, 0xd9, 0x37, 0xa0, 0x19, + 0xee, 0xa9, 0x38, 0x9b, 0x07, 0x3f, 0xfe, 0x9d, 0x56, 0x99, 0xb7, 0x5e, 0x62, 0x55, 0x49, 0xc6, + 0x1a, 0x7b, 0xb1, 0xb1, 0xf6, 0x21, 0x59, 0x45, 0x96, 0x1b, 0xea, 0x11, 0xc3, 0xa5, 0xd5, 0x0c, + 0x55, 0x4e, 0xd6, 0xe7, 0x6c, 0xd8, 0x00, 0xf2, 0xcd, 0xe9, 0xe4, 0xc8, 0xdb, 0x4e, 0x35, 0x20, + 0x76, 0xda, 0x69, 0xe0, 0x27, 0x0e, 0xbc, 0x06, 0x6c, 0x26, 0x01, 0xc2, 0x3c, 0x2a, 0xfb, 0xb2, + 0x7c, 0x38, 0x90, 0x8a, 0x28, 0x8e, 0xec, 0x30, 0x3b, 0x15, 0x66, 0x7c, 0x1b, 0x6a, 0x3c, 0x1a, + 0x86, 0xf7, 0x5b, 0x40, 0x4a, 0x52, 0x59, 0xab, 0x12, 0x90, 0xf7, 0x53, 0x40, 0x02, 0x46, 0x18, + 0x87, 0xe1, 0x39, 0x69, 0x49, 0x89, 0x80, 0xe1, 0xd6, 0x60, 0x78, 0xae, 0x95, 0xcd, 0xe8, 0x27, + 0x6e, 0xfe, 0xc7, 0xb6, 0x6b, 0xe2, 0xd4, 0x0b, 0x8d, 0x49, 0x50, 0xaf, 0xd3, 0xca, 0xac, 0x08, + 0xd8, 0xd0, 0x98, 0x04, 0xec, 0x03, 0xa8, 0x1a, 0xdc, 0xd8, 0xe0, 0xf5, 0xbe, 0x26, 0xfb, 0xb2, + 0x24, 0x33, 0x44, 0xab, 0x18, 0x92, 0x4d, 0xf2, 0x31, 0xb0, 0xe8, 0x4c, 0x53, 0xb2, 0xfc, 0xaf, + 0x2f, 0xcd, 0xc6, 0x4d, 0x71, 0xa8, 0x19, 0xdb, 0xfd, 0xb7, 0xa0, 0xc2, 0x7d, 0x1b, 0x7a, 0x10, + 0x5a, 0xb3, 0xfa, 0x2b, 0x54, 0x21, 0xe0, 0xa0, 0x41, 0x68, 0xcd, 0xd8, 0xc7, 0x50, 0x4b, 0x5b, + 0x59, 0x37, 0x56, 0x1c, 0x0d, 0xd2, 0xb4, 0xd0, 0xaa, 0x23, 0xd9, 0xee, 0xba, 0xcd, 0x03, 0xdd, + 0x49, 0x43, 0x22, 0x46, 0x7e, 0xfc, 0x55, 0x75, 0xbd, 0xb0, 0x19, 0xc1, 0xb0, 0x03, 0x23, 0x93, + 0x3c, 0x3c, 0x27, 0xa5, 0x2a, 0xee, 0xc0, 0xd8, 0x74, 0x44, 0xe3, 0x28, 0xb2, 0x22, 0x5b, 0xc0, + 0xa3, 0x3f, 0x68, 0x93, 0xb7, 0x7c, 0x1e, 0xe9, 0x41, 0x3a, 0x54, 0x7c, 0x5a, 0xb8, 0xb8, 0xf7, + 0x68, 0x3c, 0x2a, 0x46, 0xde, 0x8d, 0x3e, 0x81, 0x8d, 0x99, 0x6f, 0xe9, 0x52, 0xc9, 0xaa, 0xdc, + 0xa8, 0x43, 0xdf, 0x4a, 0x0a, 0xaf, 0xce, 0xa4, 0x14, 0xfb, 0x01, 0x6c, 0x49, 0x9c, 0xf3, 0x53, + 0x62, 0xbe, 0x4d, 0xcc, 0xdb, 0x0b, 0xcc, 0x47, 0xa7, 0xc8, 0xbe, 0x31, 0x4b, 0xa5, 0xd9, 0x36, + 0xe4, 0x3b, 0x41, 0xdb, 0x35, 0x49, 0xb7, 0x2a, 0x69, 0x3c, 0xc1, 0x1e, 0x41, 0x95, 0x1b, 0x32, + 0x14, 0x2c, 0x1b, 0xd4, 0xef, 0xca, 0xce, 0x6a, 0xb2, 0x66, 0x08, 0xa1, 0x55, 0x9c, 0xf8, 0x77, + 0xc0, 0x3e, 0x82, 0x2d, 0x7e, 0x92, 0x20, 0x8b, 0xc8, 0x37, 0x96, 0x87, 0x9c, 0x88, 0xf6, 0x12, + 0x39, 0xa9, 0xc1, 0x35, 0x7f, 0xee, 0x92, 0x71, 0x23, 0x38, 0xb9, 0x8f, 0x8b, 0xf8, 0xef, 0x11, + 0xff, 0x55, 0xb1, 0xba, 0x38, 0x19, 0xe7, 0x25, 0xd9, 0x74, 0xc5, 0x97, 0x41, 0x87, 0xc8, 0xf7, + 0x9c, 0x3c, 0xb9, 0xef, 0x87, 0xf2, 0x7c, 0xf3, 0xdb, 0xe4, 0x49, 0x7e, 0x21, 0xca, 0x93, 0x41, + 0x6e, 0x3e, 0xb7, 0x4d, 0xd2, 0xf4, 0xaa, 0x1a, 0xfd, 0x66, 0xaf, 0xc3, 0x86, 0x6f, 0x8d, 0xe6, + 0x7e, 0x60, 0x3f, 0xb3, 0xf4, 0xc0, 0x76, 0x4f, 0x49, 0xc7, 0x2b, 0x69, 0xb5, 0x18, 0x3a, 0xb0, + 0xdd, 0x53, 0x14, 0x19, 0xd6, 0x79, 0x68, 0xf9, 0x2e, 0xbf, 0x21, 0xf0, 0xb6, 0x2c, 0x32, 0xda, + 0x84, 0xc0, 0x75, 0xae, 0x81, 0x15, 0xff, 0x5e, 0x18, 0xd9, 0x80, 0x8f, 0xec, 0x83, 0x6f, 0x34, + 0xb2, 0x03, 0x1a, 0xd9, 0xbb, 0x50, 0xb2, 0xdd, 0xd0, 0xf2, 0x9f, 0x19, 0x4e, 0xfd, 0xdd, 0x25, + 0x69, 0x1c, 0xe3, 0xd8, 0x1d, 0x28, 0x06, 0x8e, 0x8d, 0xeb, 0xbd, 0xfe, 0xde, 0x12, 0x59, 0x84, + 0x62, 0xf7, 0xa0, 0x1c, 0x5f, 0x84, 0xab, 0xbf, 0xbf, 0x44, 0x97, 0x20, 0xd9, 0x4d, 0xc8, 0x9d, + 0xe1, 0x84, 0x7a, 0xb8, 0x7c, 0xb8, 0x80, 0x70, 0xdc, 0xbe, 0xc7, 0xa8, 0xb3, 0xd3, 0xf6, 0xfd, + 0x68, 0x69, 0xfb, 0xde, 0xb3, 0x1d, 0x87, 0x6f, 0xdf, 0x63, 0xf1, 0x0b, 0x37, 0x3f, 0xe2, 0xc0, + 0x96, 0x7c, 0xb0, 0xbc, 0xf9, 0x21, 0xee, 0x09, 0x5d, 0x19, 0xac, 0x04, 0xe4, 0x31, 0xe7, 0x8e, + 0xff, 0x0f, 0xe5, 0xbe, 0x4a, 0xbb, 0xd2, 0x35, 0x08, 0xe2, 0x34, 0x1a, 0x20, 0xe2, 0xbc, 0x00, + 0x95, 0xa6, 0x8f, 0xf8, 0xfd, 0x12, 0x0e, 0xe9, 0x98, 0xe7, 0xec, 0x3d, 0xa8, 0x45, 0x81, 0x48, + 0x58, 0x5c, 0x50, 0xff, 0x78, 0xa9, 0x06, 0x69, 0x02, 0xd6, 0x82, 0xea, 0x18, 0x75, 0xf7, 0x29, + 0x57, 0xe5, 0xeb, 0x9f, 0x50, 0x45, 0x76, 0xa2, 0x8d, 0xf5, 0x79, 0xaa, 0xbe, 0x96, 0xe2, 0x62, + 0x0f, 0x80, 0xd9, 0x63, 0x3e, 0x9e, 0x7b, 0xbe, 0x37, 0xe5, 0xea, 0x7a, 0xfd, 0x53, 0xe1, 0xdd, + 0x5a, 0xc2, 0xd0, 0xf1, 0xa1, 0xe5, 0x9a, 0xfa, 0x34, 0x10, 0x6a, 0xc2, 0xf7, 0xa8, 0x9e, 0x42, + 0x78, 0xc5, 0x17, 0x66, 0x23, 0xf7, 0x30, 0xd2, 0x1e, 0x04, 0x5c, 0x6b, 0xf8, 0x14, 0x70, 0xba, + 0x3e, 0x4b, 0x58, 0x7f, 0xed, 0x85, 0xac, 0x48, 0x1b, 0xb1, 0x7e, 0x02, 0x1b, 0xdc, 0xb5, 0x4a, + 0x1a, 0x19, 0x4e, 0xd1, 0xef, 0xcb, 0x92, 0x4b, 0x76, 0x3a, 0x6b, 0x55, 0x53, 0x76, 0x41, 0x7f, + 0x0c, 0x9b, 0x91, 0x77, 0x38, 0x14, 0x8e, 0xe4, 0x5f, 0x97, 0x8b, 0x8d, 0xbd, 0xaf, 0x5a, 0x6d, + 0x1e, 0xfd, 0xa4, 0x22, 0x1f, 0x41, 0x8d, 0x76, 0xd1, 0xf8, 0x46, 0xd2, 0x6f, 0xac, 0xbc, 0x91, + 0x54, 0x45, 0xa2, 0x28, 0x85, 0xc2, 0x3f, 0x59, 0xa7, 0xa3, 0xd0, 0xaa, 0xff, 0x80, 0x0b, 0xff, + 0x18, 0xd8, 0x0c, 0x2d, 0xf6, 0x08, 0xc0, 0x98, 0xcd, 0x9c, 0x0b, 0x3e, 0x35, 0x7f, 0x48, 0x53, + 0x73, 0x5b, 0x9a, 0x9a, 0x0d, 0x44, 0xd2, 0xdc, 0x2c, 0x1b, 0xd1, 0x4f, 0xf6, 0x10, 0xaa, 0x33, + 0x2f, 0x08, 0x75, 0x73, 0xea, 0x50, 0xfb, 0x1b, 0xf2, 0xda, 0x3e, 0xf4, 0x82, 0xb0, 0x35, 0x75, + 0xb0, 0x15, 0x30, 0x8b, 0x7f, 0xb3, 0x2e, 0x5c, 0xf2, 0x5c, 0xdd, 0x9c, 0xcf, 0x1c, 0x7b, 0x84, + 0x3d, 0x60, 0xd0, 0x61, 0x7d, 0x7d, 0x97, 0x4a, 0xbc, 0x21, 0x95, 0xd8, 0x77, 0x5b, 0x11, 0x91, + 0x88, 0x8a, 0xdb, 0xf2, 0x16, 0x41, 0x64, 0x67, 0xd2, 0x18, 0xc4, 0x51, 0xa4, 0x4d, 0xae, 0x1a, + 0x10, 0x34, 0x0a, 0x23, 0xfd, 0x04, 0x36, 0x13, 0x2a, 0x6c, 0x60, 0x50, 0x6f, 0xc9, 0x33, 0x59, + 0x0a, 0x4b, 0xaf, 0x45, 0x8c, 0x08, 0x0b, 0xa8, 0xef, 0x3c, 0xc7, 0x99, 0xcf, 0x84, 0x28, 0xad, + 0xb7, 0x45, 0xdf, 0x11, 0x90, 0x4b, 0x49, 0xc9, 0x14, 0xb7, 0xa6, 0xf5, 0x3d, 0xd9, 0x14, 0xb7, + 0xa6, 0xa8, 0x66, 0x88, 0x10, 0xe1, 0x67, 0xb6, 0x75, 0x16, 0xd4, 0x1f, 0x53, 0x74, 0xa3, 0x08, + 0xc2, 0x7e, 0x82, 0x20, 0xdc, 0xf7, 0x4d, 0xdb, 0x47, 0x13, 0x80, 0xc2, 0xb9, 0xf6, 0x79, 0x14, + 0x2f, 0x07, 0x21, 0x05, 0x6b, 0x42, 0x9d, 0xc4, 0xb9, 0x58, 0xab, 0xa9, 0x53, 0xbb, 0xce, 0xd2, + 0x92, 0xbc, 0x8c, 0xb4, 0xfc, 0x50, 0xf0, 0x50, 0x3a, 0xbf, 0xfb, 0x21, 0xbc, 0xba, 0x3a, 0x93, + 0xe8, 0xc2, 0xe6, 0x67, 0xa4, 0x6f, 0x5c, 0x5b, 0xc1, 0xdd, 0xa4, 0x3b, 0x9c, 0xea, 0xcf, 0xf2, + 0x50, 0x8a, 0x4c, 0x23, 0x56, 0x81, 0xe2, 0x51, 0xef, 0xf3, 0x5e, 0xff, 0x69, 0x8f, 0xdf, 0x5b, + 0x6c, 0x0c, 0x06, 0x6d, 0x6d, 0xa8, 0x98, 0x6c, 0x03, 0x80, 0xee, 0x02, 0xe9, 0x83, 0x66, 0xa3, + 0xc7, 0xef, 0x31, 0xd2, 0x0d, 0x24, 0x9e, 0x5e, 0x67, 0x5b, 0x50, 0xdb, 0x3b, 0xea, 0x51, 0x24, + 0x22, 0x07, 0x65, 0x11, 0xd4, 0xfe, 0x82, 0x9f, 0x89, 0x72, 0x50, 0x0e, 0x41, 0x07, 0x8d, 0x61, + 0x5b, 0xeb, 0x44, 0xa0, 0x3c, 0x05, 0x35, 0xf6, 0x8f, 0xb4, 0xa6, 0xc8, 0xa9, 0xc0, 0x2e, 0xc3, + 0x56, 0xcc, 0x16, 0x65, 0xa9, 0x14, 0xb1, 0x66, 0x87, 0x5a, 0xff, 0xb3, 0x76, 0x73, 0xa8, 0x00, + 0x1d, 0xb0, 0x3e, 0x7e, 0xac, 0x54, 0x58, 0x15, 0x4a, 0xad, 0xce, 0x60, 0xd8, 0xe9, 0x35, 0x87, + 0x4a, 0x15, 0x2b, 0xbc, 0xd7, 0xe9, 0x0e, 0xdb, 0x9a, 0x52, 0x63, 0x25, 0xc8, 0x7d, 0xd6, 0xef, + 0xf4, 0x94, 0x0d, 0xba, 0x13, 0xd5, 0x38, 0x38, 0xec, 0xb6, 0x95, 0x4d, 0x84, 0x0e, 0xfa, 0xda, + 0x50, 0x51, 0x10, 0xfa, 0xb4, 0xd3, 0x6b, 0xf5, 0x9f, 0x2a, 0x5b, 0xac, 0x0c, 0xf9, 0xa3, 0x1e, + 0x16, 0xc3, 0x58, 0x0d, 0xca, 0xf4, 0x53, 0x6f, 0x74, 0xbb, 0xca, 0x25, 0xe9, 0x54, 0x76, 0x1b, + 0x51, 0x74, 0xc6, 0x3b, 0xc0, 0x3a, 0x5c, 0xc6, 0xb6, 0xc4, 0x49, 0xa2, 0xbe, 0x82, 0xf9, 0x1c, + 0x74, 0x7a, 0x47, 0x03, 0xe5, 0x2a, 0x12, 0xd3, 0x4f, 0xc2, 0xd4, 0x31, 0x9f, 0x4e, 0x8f, 0xba, + 0xf2, 0x26, 0xfe, 0x6e, 0xb5, 0xbb, 0xed, 0x61, 0x5b, 0xb9, 0x85, 0xad, 0xea, 0xf6, 0x9b, 0x9f, + 0xeb, 0xfd, 0x43, 0xe5, 0x35, 0xec, 0xd3, 0x43, 0xad, 0xad, 0x0b, 0xc2, 0xdb, 0x4c, 0x81, 0xea, + 0xde, 0xd1, 0x8f, 0x7f, 0xfc, 0xa5, 0x2e, 0x1a, 0xf5, 0x3a, 0x96, 0x99, 0x50, 0xe8, 0x47, 0x9f, + 0x2b, 0x77, 0x17, 0x40, 0x83, 0xcf, 0x95, 0x37, 0xb0, 0x53, 0xa2, 0x5e, 0x56, 0xee, 0x21, 0x81, + 0xd6, 0x6e, 0x1e, 0x69, 0x83, 0xce, 0x93, 0xb6, 0xde, 0x1c, 0xb6, 0x95, 0x37, 0xa9, 0x17, 0x3a, + 0xbd, 0xcf, 0x95, 0xfb, 0x58, 0x4d, 0xfc, 0xc5, 0xfb, 0xfe, 0x2d, 0xc6, 0x60, 0x23, 0xa1, 0x25, + 0xd8, 0xdb, 0x48, 0xb2, 0xab, 0xf5, 0x1b, 0xad, 0x66, 0x63, 0x30, 0x54, 0xde, 0xc1, 0x36, 0x0e, + 0x0e, 0xbb, 0x9d, 0xa1, 0xf2, 0x00, 0x1b, 0xf2, 0xb8, 0x31, 0xdc, 0x6f, 0x6b, 0xca, 0xbb, 0x38, + 0x8c, 0xc3, 0xce, 0x41, 0x5b, 0x17, 0x7d, 0xfa, 0x10, 0xcb, 0xd8, 0xeb, 0x74, 0xbb, 0xca, 0x23, + 0x3a, 0x55, 0x6c, 0x68, 0xc3, 0x0e, 0x0d, 0xe4, 0x07, 0x98, 0x41, 0xe3, 0xf0, 0xb0, 0xfb, 0xa5, + 0xf2, 0x21, 0x36, 0xf0, 0xe0, 0xa8, 0x3b, 0xec, 0xe8, 0x47, 0x87, 0xad, 0xc6, 0xb0, 0xad, 0x7c, + 0x44, 0xa3, 0xdc, 0x1f, 0x0c, 0x5b, 0x07, 0x5d, 0xe5, 0x63, 0xca, 0x93, 0xe6, 0x58, 0xb3, 0xdb, + 0xef, 0xb5, 0x95, 0x4f, 0xd4, 0x5c, 0x69, 0x47, 0xd9, 0x51, 0x73, 0x25, 0x55, 0x51, 0xd5, 0xdf, + 0x86, 0x52, 0x64, 0x17, 0x63, 0x96, 0x9d, 0x5e, 0xaf, 0xad, 0x29, 0x6b, 0x58, 0x6c, 0xb7, 0xbd, + 0x37, 0x54, 0x32, 0x74, 0xdc, 0xda, 0x79, 0xbc, 0x3f, 0x54, 0xd6, 0xf1, 0x67, 0xff, 0x08, 0x7b, 0x30, 0x4b, 0x4d, 0x6f, 0x1f, 0x74, 0x94, 0x1c, 0xfe, 0x6a, 0xf4, 0x86, 0x1d, 0x25, 0x4f, 0x13, - 0xa4, 0xd3, 0xdb, 0xeb, 0xb6, 0x95, 0x02, 0x42, 0x0f, 0x1a, 0xda, 0xe7, 0x4a, 0x91, 0x67, 0xda, - 0x6a, 0x7f, 0xa1, 0x94, 0x58, 0x01, 0xd6, 0xbb, 0x8f, 0x94, 0x32, 0x82, 0x5a, 0xed, 0xd6, 0xd1, - 0xa1, 0x02, 0xea, 0x7d, 0x28, 0x36, 0x26, 0x93, 0x03, 0xcf, 0xa4, 0xf3, 0xda, 0xdd, 0xa3, 0x6e, - 0x97, 0x2f, 0x98, 0x9d, 0xfe, 0x70, 0xd8, 0x3f, 0x50, 0x32, 0x38, 0x45, 0x87, 0xfd, 0x43, 0x65, - 0x5d, 0xed, 0x40, 0x29, 0xd2, 0x00, 0xa4, 0xeb, 0x79, 0x25, 0xc8, 0x1d, 0x6a, 0xed, 0x27, 0xfc, - 0xc4, 0xbf, 0xd7, 0xfe, 0x02, 0xab, 0x89, 0xbf, 0x30, 0xa3, 0x2c, 0x16, 0xc4, 0xef, 0xd1, 0xd1, - 0xfd, 0xbc, 0x6e, 0xa7, 0xd7, 0x6e, 0x68, 0x4a, 0x5e, 0xfd, 0x10, 0xb6, 0x96, 0xe4, 0x27, 0x15, - 0xdf, 0xe8, 0x88, 0xe2, 0x3b, 0x7b, 0xbd, 0xbe, 0xd6, 0xe6, 0x17, 0xfe, 0x44, 0xa7, 0xae, 0xab, - 0x6f, 0x41, 0x39, 0x16, 0xf4, 0x38, 0xc9, 0x9a, 0x5a, 0x7f, 0x30, 0xe0, 0x63, 0xb0, 0x86, 0x69, - 0xea, 0x1b, 0x9e, 0xce, 0x7c, 0x96, 0x2b, 0xdd, 0x56, 0xb6, 0x3f, 0xcb, 0x95, 0xee, 0x2a, 0xaf, - 0xab, 0x03, 0xd8, 0x8a, 0xf6, 0x1b, 0xba, 0x6c, 0x40, 0x86, 0xd0, 0x65, 0xc8, 0x77, 0xad, 0x67, - 0x96, 0x13, 0x45, 0xcd, 0x53, 0x02, 0xa1, 0xfd, 0xe3, 0xaf, 0x3a, 0xf1, 0x6d, 0x71, 0x4a, 0xa0, - 0x82, 0xd9, 0x93, 0x2e, 0xac, 0xd3, 0x1d, 0xcb, 0xff, 0x37, 0x03, 0xa5, 0x78, 0x17, 0xbb, 0x0b, - 0xeb, 0xc3, 0x81, 0x38, 0xb2, 0xba, 0xfc, 0x30, 0x79, 0x9f, 0x63, 0x18, 0xfd, 0xd2, 0xd6, 0x87, - 0x03, 0xf6, 0x36, 0x14, 0xf8, 0x9d, 0x59, 0xe1, 0xae, 0xba, 0x9c, 0xde, 0x19, 0x87, 0x84, 0xd3, - 0x04, 0x0d, 0xfb, 0x10, 0xca, 0x71, 0x6d, 0x85, 0xff, 0xe6, 0x5a, 0x9a, 0x21, 0x46, 0x6b, 0x09, - 0xa5, 0xda, 0x85, 0x8d, 0x74, 0x86, 0xec, 0x16, 0x00, 0xcf, 0x52, 0x72, 0x52, 0x4a, 0x10, 0x76, - 0x03, 0xa2, 0xab, 0xbc, 0x2d, 0xaa, 0x58, 0x2d, 0xbe, 0xda, 0xdb, 0x52, 0xff, 0x59, 0x16, 0x20, - 0xd1, 0x83, 0xb1, 0x23, 0x62, 0xaf, 0x54, 0x5e, 0x9c, 0xc9, 0xbf, 0x02, 0x65, 0xc7, 0x33, 0x4c, - 0xf9, 0x79, 0x8e, 0x12, 0x02, 0x68, 0x98, 0xe4, 0x4b, 0x67, 0x65, 0x1e, 0x10, 0xc3, 0xae, 0x42, - 0x61, 0xec, 0xf9, 0x53, 0x23, 0x14, 0x57, 0x24, 0x44, 0x0a, 0xb7, 0x33, 0x7b, 0xe2, 0x7a, 0xbe, - 0x85, 0xd6, 0x80, 0x4b, 0xb7, 0x24, 0x70, 0x0c, 0xaa, 0x02, 0xd8, 0x45, 0x18, 0x6e, 0x47, 0x96, - 0x3b, 0x72, 0xbc, 0xc0, 0x32, 0x71, 0x83, 0x29, 0x90, 0xca, 0x0f, 0x11, 0x68, 0xe7, 0x82, 0xb7, - 0xd6, 0x9f, 0xda, 0xae, 0x11, 0x8a, 0x63, 0x2a, 0x6a, 0x6d, 0x04, 0xc1, 0xea, 0x7e, 0x15, 0x78, - 0xc2, 0x49, 0xc5, 0x6f, 0x58, 0x95, 0x10, 0x40, 0xd5, 0x7d, 0x15, 0xc0, 0x0a, 0x46, 0xc6, 0x8c, - 0x67, 0x5e, 0xe6, 0xd7, 0x5c, 0x05, 0x64, 0xe7, 0x82, 0x75, 0x61, 0x63, 0x78, 0x8c, 0xdb, 0xaf, - 0xd7, 0x32, 0x42, 0xa3, 0xe9, 0x39, 0xc2, 0x7d, 0x74, 0x77, 0xd1, 0x60, 0x78, 0x98, 0x26, 0xe3, - 0x91, 0xa1, 0x0b, 0xbc, 0x68, 0xc0, 0xdb, 0x23, 0xeb, 0xd8, 0xf2, 0x27, 0xdc, 0xf8, 0xa8, 0xa4, - 0xee, 0x36, 0x70, 0x0c, 0x59, 0x1f, 0x15, 0x3b, 0x49, 0xdc, 0x68, 0xc0, 0xa5, 0x15, 0x99, 0x7f, - 0xab, 0xd0, 0xc2, 0x7f, 0xb4, 0x0e, 0x15, 0x29, 0x7f, 0x3a, 0x27, 0x30, 0x66, 0xe4, 0xe7, 0x8a, - 0x63, 0xab, 0xcb, 0x02, 0xc2, 0x6f, 0x92, 0x8c, 0x8c, 0xd0, 0x70, 0xbc, 0x89, 0x74, 0x47, 0x46, - 0x40, 0x3a, 0x26, 0x1d, 0xdd, 0x19, 0x53, 0x2b, 0x98, 0x19, 0xa3, 0x68, 0x85, 0x24, 0x80, 0xe4, - 0x5d, 0x98, 0x9c, 0xfc, 0x2e, 0x8c, 0xc2, 0xcf, 0x78, 0x79, 0x90, 0x0c, 0x1d, 0xe9, 0xde, 0x86, - 0x4a, 0xa4, 0x37, 0x62, 0x29, 0xe2, 0x18, 0x32, 0x02, 0x75, 0x4c, 0x76, 0x0f, 0x92, 0x77, 0x6f, - 0x74, 0x23, 0xd0, 0xbd, 0x71, 0x14, 0xa1, 0x17, 0x83, 0x1b, 0x41, 0x7f, 0x1c, 0x9f, 0x0d, 0x4c, - 0x3d, 0x33, 0x1e, 0x5f, 0x04, 0x90, 0x8c, 0x7b, 0x08, 0x97, 0x84, 0xc3, 0xd2, 0x32, 0xf5, 0xb1, - 0x6d, 0x39, 0xa6, 0x6e, 0x9b, 0xfc, 0x04, 0x3d, 0xaf, 0x6d, 0xc5, 0xa8, 0x5d, 0xc4, 0x74, 0x4c, - 0xd2, 0xb0, 0x84, 0x95, 0x6a, 0xda, 0x13, 0x2b, 0x08, 0x45, 0x3c, 0x59, 0x95, 0x03, 0x5b, 0x04, - 0x53, 0x9d, 0x68, 0x89, 0x34, 0xc2, 0x90, 0x6e, 0xb1, 0xc5, 0xea, 0x5e, 0x26, 0xba, 0xe0, 0xc2, - 0x35, 0xbd, 0x57, 0x28, 0x16, 0x4b, 0x04, 0x14, 0x8b, 0x95, 0x12, 0x07, 0x0a, 0xdf, 0x83, 0x4d, - 0x44, 0x8a, 0x4a, 0x11, 0x09, 0xbf, 0xd9, 0x54, 0x1b, 0x79, 0x0e, 0xaf, 0x10, 0x02, 0xd5, 0xff, - 0x3f, 0x0f, 0x90, 0x18, 0xfa, 0x74, 0x69, 0x8e, 0x3c, 0x70, 0xf1, 0xc8, 0x15, 0x29, 0xdd, 0x31, - 0xd9, 0x23, 0xb8, 0x2a, 0xae, 0xe8, 0xc5, 0x41, 0x1e, 0xb6, 0xab, 0x1f, 0x1b, 0x51, 0x50, 0x19, - 0x13, 0x58, 0x7e, 0x7c, 0xdf, 0x71, 0x77, 0x0c, 0x34, 0x1b, 0x36, 0x65, 0x9e, 0xf0, 0x62, 0x96, - 0xf6, 0xf8, 0xcb, 0xca, 0x68, 0xc2, 0x3e, 0xbc, 0x98, 0xb1, 0xf7, 0xe0, 0x8a, 0x6f, 0x8d, 0x7d, - 0x2b, 0x38, 0xd1, 0xc3, 0x40, 0x2e, 0x8c, 0x47, 0x7d, 0x6e, 0x09, 0xe4, 0x30, 0x88, 0xcb, 0x7a, - 0x0f, 0xae, 0x88, 0xce, 0x5d, 0xa8, 0x1e, 0xf7, 0xa4, 0x6f, 0x71, 0xa4, 0x5c, 0x3b, 0x0a, 0x05, - 0x26, 0xef, 0x47, 0xf4, 0xd2, 0x50, 0x49, 0x2b, 0x73, 0x4f, 0x87, 0xb8, 0x53, 0x4f, 0x2e, 0x0c, - 0x5a, 0xb8, 0x25, 0x8d, 0x27, 0x98, 0x0a, 0x39, 0x1c, 0x7b, 0x1a, 0xba, 0x8d, 0x47, 0x1b, 0x0f, - 0xe9, 0x25, 0x25, 0x7a, 0x0f, 0xc1, 0x33, 0x2d, 0x8d, 0x70, 0xec, 0x1d, 0x9c, 0x17, 0x49, 0xb3, - 0xa3, 0xc7, 0x40, 0x78, 0x9c, 0x83, 0x92, 0x34, 0x54, 0xe3, 0xcf, 0x82, 0xbc, 0x05, 0x4c, 0xaa, - 0x79, 0x44, 0x5d, 0x25, 0xea, 0xcd, 0xb8, 0xda, 0x82, 0xf8, 0x0d, 0xa0, 0x2a, 0xf2, 0x63, 0xb4, - 0xda, 0xb2, 0xbd, 0x8f, 0x48, 0x3a, 0x51, 0x7b, 0x0f, 0xae, 0x24, 0xad, 0xd3, 0x8d, 0x50, 0x0f, - 0x4f, 0x2c, 0xdd, 0x72, 0x4d, 0xba, 0x57, 0x59, 0xd2, 0xb6, 0xe2, 0x86, 0x36, 0xc2, 0xe1, 0x89, - 0x85, 0x16, 0xbb, 0xe4, 0x51, 0xdd, 0x7c, 0xb1, 0x47, 0xf5, 0x23, 0xa8, 0xa7, 0x75, 0x6a, 0xa9, - 0xbb, 0xf9, 0xbd, 0xe4, 0xcb, 0x72, 0xac, 0x48, 0xdc, 0xe3, 0x0f, 0x60, 0xeb, 0xc4, 0x08, 0xd2, - 0xfa, 0x38, 0x39, 0x7a, 0x4b, 0xda, 0xe6, 0x89, 0x11, 0xc8, 0x4a, 0xb8, 0xfa, 0x07, 0x19, 0xd8, - 0x48, 0xbb, 0x3e, 0xf8, 0x65, 0x2f, 0x7e, 0x0d, 0x2a, 0x43, 0x6b, 0x2c, 0x4a, 0xe2, 0x5a, 0xe0, - 0x01, 0x25, 0xf3, 0xa9, 0x1b, 0xad, 0x85, 0xd9, 0x29, 0xbf, 0x1c, 0xc5, 0xde, 0x84, 0xe2, 0xec, - 0x94, 0x4b, 0xe8, 0xe7, 0xcd, 0xbe, 0xc2, 0x8c, 0x07, 0xc2, 0xbf, 0x09, 0xc5, 0xf9, 0x69, 0xf2, - 0xe2, 0xc0, 0x4a, 0xd2, 0x39, 0x91, 0xaa, 0xff, 0x78, 0x1d, 0xaa, 0xb2, 0xc7, 0xee, 0x9b, 0x44, - 0x9a, 0x7c, 0xab, 0x20, 0xa0, 0x6d, 0x8a, 0x55, 0xd5, 0x29, 0xd2, 0x1c, 0xfb, 0x89, 0x87, 0x99, - 0xc0, 0x89, 0x11, 0x34, 0xe6, 0xa1, 0x87, 0x22, 0x1f, 0x45, 0xa9, 0xe7, 0x44, 0xd1, 0xf8, 0x7c, - 0x65, 0xa0, 0x4c, 0x10, 0x81, 0xf8, 0xef, 0x89, 0xcb, 0x3e, 0x74, 0x13, 0x90, 0xc2, 0x3a, 0xf3, - 0x4b, 0xf3, 0xa5, 0x1a, 0x5d, 0x04, 0xa4, 0x30, 0xd6, 0x47, 0xb0, 0x99, 0x5c, 0xad, 0xe0, 0x2c, - 0x85, 0x25, 0x96, 0x5a, 0x7c, 0xaf, 0x42, 0xbc, 0x52, 0x50, 0xb3, 0x03, 0xdd, 0x73, 0xcc, 0xe8, - 0x0e, 0x57, 0x31, 0x3a, 0x4f, 0xe9, 0x3b, 0xa6, 0xb8, 0x7e, 0xca, 0x69, 0x5c, 0xeb, 0x2c, 0xa2, - 0x89, 0xcf, 0x5c, 0x7a, 0xd6, 0x99, 0xb8, 0xcb, 0xf5, 0x67, 0x19, 0xd8, 0x5a, 0x72, 0xd2, 0xa1, - 0x68, 0x4f, 0x5e, 0xf0, 0xc2, 0x9f, 0x68, 0x64, 0x4e, 0x8d, 0x70, 0x74, 0xa2, 0xcf, 0x7c, 0x6b, - 0x6c, 0x9f, 0x47, 0xcf, 0x90, 0x11, 0xec, 0x90, 0x40, 0x28, 0xfd, 0xf9, 0xb9, 0x21, 0x3f, 0xc6, - 0xe0, 0x82, 0x8f, 0x9f, 0x15, 0x76, 0xe9, 0xfc, 0x22, 0x0a, 0x73, 0xcd, 0x3d, 0x27, 0xcc, 0xf5, - 0x06, 0x94, 0x5d, 0x8f, 0x42, 0x9a, 0x66, 0xa7, 0x22, 0x16, 0xac, 0xe8, 0x7a, 0x61, 0xdf, 0x3d, - 0x3c, 0x65, 0x8f, 0xe0, 0xca, 0x3c, 0xa0, 0xb8, 0x81, 0x63, 0xcb, 0x0f, 0x4e, 0xec, 0xd8, 0x5c, - 0xe6, 0x02, 0xe4, 0xd2, 0x3c, 0xb0, 0x0e, 0x62, 0x1c, 0x6f, 0x89, 0x7a, 0x13, 0x0a, 0x9d, 0xd8, - 0xb9, 0x18, 0xc7, 0xd5, 0x65, 0xc5, 0xdb, 0x3d, 0x1e, 0x94, 0xb9, 0xd5, 0x79, 0x60, 0xcc, 0xd8, - 0x03, 0xc8, 0x4e, 0x8d, 0x99, 0x38, 0xac, 0xac, 0xc7, 0x27, 0xba, 0x1c, 0xfb, 0xf0, 0xc0, 0x98, - 0xf1, 0xdd, 0x1f, 0x89, 0x6e, 0x7c, 0x04, 0xa5, 0x08, 0xf0, 0xad, 0x76, 0xec, 0x7f, 0xb9, 0x0e, - 0xe5, 0x96, 0x7c, 0x38, 0x30, 0x32, 0x5c, 0x3d, 0xf4, 0xe7, 0x2e, 0x2a, 0xd0, 0xd1, 0x2b, 0x25, - 0x23, 0xc3, 0x1d, 0x0a, 0x50, 0x34, 0xb5, 0xd7, 0x5f, 0x30, 0xb5, 0x6f, 0x02, 0xf8, 0xe4, 0x5b, - 0x23, 0xf7, 0x5a, 0x36, 0x8e, 0x33, 0xee, 0x98, 0x1d, 0xf3, 0x7c, 0x75, 0x88, 0x55, 0xee, 0x9b, - 0x87, 0x58, 0xe5, 0x57, 0x86, 0x58, 0xdd, 0x4b, 0xb6, 0x17, 0x9c, 0xe2, 0x58, 0x70, 0x99, 0x6f, - 0x72, 0xb3, 0xf8, 0x6a, 0x13, 0x96, 0xfe, 0x3d, 0xd8, 0x88, 0x5a, 0x27, 0xf2, 0x83, 0xd4, 0x6d, - 0x2a, 0x81, 0xe3, 0xa7, 0x09, 0xb5, 0x50, 0x4e, 0xa6, 0x97, 0x6c, 0xe5, 0x25, 0x61, 0x5c, 0x7f, - 0x39, 0x03, 0x4c, 0xf8, 0x82, 0x76, 0xe7, 0x8e, 0x33, 0xb4, 0xce, 0x49, 0x32, 0x3c, 0x80, 0x2d, - 0x71, 0xd8, 0x21, 0x5d, 0x78, 0x15, 0xc7, 0xfb, 0x1c, 0x91, 0x1c, 0xef, 0xaf, 0xba, 0x1b, 0xbb, - 0xbe, 0xf2, 0x6e, 0xec, 0xea, 0x3b, 0xb7, 0xb7, 0xa1, 0x22, 0xdf, 0x2c, 0xe5, 0x7a, 0x12, 0x18, - 0xf1, 0xa5, 0x52, 0xf5, 0xcf, 0xd6, 0x01, 0x12, 0x7f, 0xd5, 0xaf, 0x3a, 0x3e, 0x6e, 0xc5, 0x90, - 0x64, 0x57, 0x0d, 0xc9, 0x7d, 0x50, 0x64, 0x3a, 0xe9, 0x8a, 0xf3, 0x46, 0x42, 0x18, 0xa9, 0x39, - 0x76, 0x20, 0xdf, 0x2d, 0xa5, 0xeb, 0x27, 0x22, 0xa4, 0x88, 0x23, 0xb9, 0xf3, 0x5c, 0x2c, 0xc0, - 0x92, 0x1d, 0x70, 0x99, 0xcc, 0x3e, 0x85, 0xeb, 0x31, 0xa7, 0x7e, 0x66, 0x87, 0x27, 0xde, 0x3c, - 0x14, 0x8b, 0x35, 0x10, 0x52, 0xea, 0x6a, 0x94, 0xd3, 0x53, 0x8e, 0xe6, 0xeb, 0x35, 0x40, 0x6b, - 0x69, 0x3c, 0x77, 0x1c, 0x3d, 0xb4, 0xce, 0x43, 0xf1, 0x2c, 0x49, 0x3d, 0xe5, 0xea, 0x93, 0x86, - 0x57, 0x2b, 0x8d, 0x45, 0x42, 0xfd, 0x1f, 0x59, 0xc8, 0xff, 0x68, 0x6e, 0xf9, 0x17, 0xec, 0x23, - 0x28, 0x07, 0xe1, 0x34, 0x94, 0x4f, 0xdd, 0xc5, 0xe3, 0x35, 0x84, 0xa7, 0x43, 0x73, 0x6b, 0x6a, - 0xb9, 0x21, 0xf7, 0x81, 0x23, 0x2d, 0x6d, 0x40, 0x97, 0x21, 0x1f, 0x84, 0xd6, 0x2c, 0x10, 0xb1, - 0xb3, 0x3c, 0xc1, 0xb6, 0x21, 0xef, 0x7a, 0xa6, 0x15, 0xa4, 0x23, 0x64, 0x7b, 0xa8, 0x71, 0x70, - 0x04, 0x53, 0xa1, 0x10, 0x8f, 0xf8, 0xd2, 0xc9, 0x37, 0xc7, 0xd0, 0xbd, 0x1b, 0xcb, 0x30, 0x6d, - 0x77, 0x12, 0x5d, 0x19, 0x8f, 0xd3, 0xb8, 0xb5, 0x92, 0x95, 0x65, 0x4c, 0xa2, 0xa7, 0x26, 0x44, - 0x92, 0x6d, 0x43, 0x05, 0x7f, 0x3e, 0xf5, 0xed, 0xd0, 0x1a, 0x3c, 0x8e, 0xa4, 0xbb, 0x04, 0x42, - 0x1b, 0xc9, 0xb4, 0x42, 0x6b, 0x14, 0x0e, 0xbe, 0x16, 0x71, 0xa7, 0x65, 0x4d, 0x82, 0xb0, 0xef, - 0x01, 0x3b, 0x36, 0x46, 0xa7, 0x13, 0x9f, 0xa2, 0x45, 0xbe, 0x9e, 0x5b, 0xbe, 0x6d, 0x45, 0x71, - 0xa6, 0x15, 0xa9, 0x53, 0xb4, 0xad, 0x84, 0xec, 0x47, 0x9c, 0x0a, 0xad, 0xbb, 0xa9, 0x71, 0xde, - 0xf2, 0x66, 0x22, 0x8c, 0x4f, 0xa4, 0xd8, 0x63, 0xb8, 0x8a, 0x9b, 0x63, 0x74, 0x31, 0x19, 0xe7, - 0x90, 0xf0, 0xae, 0xf2, 0x57, 0xaa, 0x2e, 0x9d, 0x18, 0x41, 0x72, 0x17, 0x91, 0x3b, 0x05, 0xd4, - 0xdf, 0x82, 0x5a, 0xaa, 0xdf, 0x97, 0x1c, 0x7b, 0x83, 0x76, 0xb7, 0xdd, 0x1c, 0x72, 0x47, 0x81, - 0x70, 0x38, 0xad, 0x4b, 0x9e, 0xa9, 0x9c, 0xe4, 0x40, 0xc8, 0x93, 0x5f, 0xab, 0xad, 0xed, 0xb5, - 0x95, 0x82, 0x9a, 0x2b, 0x65, 0x95, 0xac, 0xfa, 0x57, 0xd6, 0x61, 0x6b, 0xe8, 0x1b, 0x6e, 0x60, - 0x70, 0x1d, 0xc6, 0x0d, 0x7d, 0xcf, 0x61, 0xdf, 0x83, 0x52, 0x38, 0x72, 0xe4, 0x89, 0x70, 0x3b, - 0x12, 0x3b, 0x0b, 0xa4, 0x0f, 0x87, 0x23, 0x7e, 0x24, 0x52, 0x0c, 0xf9, 0x0f, 0xf6, 0x0e, 0xe4, - 0x8f, 0xad, 0x89, 0xed, 0x0a, 0xc9, 0x7b, 0x65, 0x91, 0x71, 0x07, 0x91, 0xfb, 0x6b, 0x1a, 0xa7, - 0x62, 0xef, 0x41, 0x61, 0xe4, 0x4d, 0xa3, 0x2d, 0x2f, 0xb9, 0x95, 0x28, 0x15, 0x84, 0xd8, 0xfd, - 0x35, 0x4d, 0xd0, 0xb1, 0x8f, 0xa0, 0xe4, 0x7b, 0x8e, 0x83, 0xfd, 0x2e, 0x36, 0xc3, 0xfa, 0x22, - 0x8f, 0x26, 0xf0, 0xfb, 0x6b, 0x5a, 0x4c, 0xab, 0x3e, 0x84, 0xa2, 0xa8, 0x2c, 0x76, 0xc3, 0x4e, - 0x7b, 0xaf, 0x23, 0x7a, 0xb0, 0xd9, 0x3f, 0x38, 0xe8, 0x0c, 0xf9, 0x95, 0x6b, 0xad, 0xdf, 0xed, - 0xee, 0x34, 0x9a, 0x9f, 0x2b, 0xeb, 0x3b, 0x25, 0x28, 0xf0, 0xe1, 0x51, 0x7f, 0x27, 0x03, 0x9b, - 0x0b, 0x0d, 0x60, 0x9f, 0x40, 0x8e, 0xec, 0x2b, 0xde, 0x3d, 0x77, 0x57, 0xb6, 0x52, 0x4a, 0x73, - 0x4d, 0x1b, 0x39, 0xd4, 0x4f, 0x61, 0x23, 0x0d, 0x97, 0x9c, 0x49, 0x35, 0x28, 0x6b, 0xed, 0x46, - 0x4b, 0xef, 0xf7, 0xba, 0x5f, 0x72, 0xdf, 0x2c, 0x25, 0x9f, 0x6a, 0x1d, 0xf2, 0xff, 0xfc, 0x26, - 0x28, 0x8b, 0x1d, 0xc3, 0xf6, 0xd0, 0x6a, 0x9a, 0xce, 0x1c, 0x8b, 0x94, 0x53, 0x69, 0xc8, 0x6e, - 0xad, 0xe8, 0x49, 0x41, 0xc6, 0xc3, 0x9c, 0x46, 0xa9, 0xb4, 0xfa, 0x5b, 0xc0, 0x96, 0x7b, 0xf0, - 0x57, 0x97, 0xfd, 0x7f, 0xcd, 0x40, 0xee, 0xd0, 0x31, 0x5c, 0x76, 0x07, 0xf2, 0xf4, 0x00, 0x92, - 0x90, 0xdf, 0xf2, 0x6a, 0xc2, 0x69, 0x41, 0x38, 0xf6, 0x16, 0x64, 0xc3, 0x51, 0x74, 0xd3, 0xfb, - 0xda, 0x73, 0x26, 0xdf, 0xfe, 0x9a, 0x86, 0x54, 0xec, 0x3e, 0x64, 0x4d, 0x33, 0xba, 0xf5, 0x20, - 0x5c, 0x4a, 0x2d, 0x23, 0x34, 0x5a, 0xd6, 0xd8, 0x76, 0x6d, 0xf1, 0x60, 0x13, 0x92, 0xb0, 0xd7, - 0x21, 0x6b, 0x8e, 0x9c, 0xf4, 0x15, 0x16, 0xee, 0x44, 0x88, 0x33, 0x34, 0x47, 0x0e, 0xea, 0x7e, - 0xa1, 0x7f, 0xa1, 0xfb, 0x73, 0x97, 0xe2, 0x65, 0x03, 0x61, 0x8f, 0x55, 0x50, 0x8b, 0x99, 0x53, - 0xd0, 0x6d, 0x20, 0xae, 0x8c, 0xce, 0x7c, 0x6b, 0x66, 0xf8, 0xb1, 0x25, 0x66, 0x07, 0x87, 0x1c, - 0xb0, 0x53, 0x00, 0x7a, 0xe5, 0x56, 0x7d, 0x9b, 0x5e, 0xe7, 0x41, 0x95, 0x5e, 0x8d, 0x7e, 0xad, - 0x78, 0x10, 0x51, 0x60, 0xd4, 0x3f, 0xcf, 0x42, 0x45, 0xaa, 0x0f, 0xfb, 0x00, 0x4a, 0x66, 0x7a, - 0x21, 0x5e, 0x5f, 0xaa, 0xf4, 0xc3, 0x56, 0xb4, 0x04, 0x4d, 0x31, 0xbd, 0xe9, 0xbc, 0x2d, 0xd4, - 0x9f, 0x19, 0xbe, 0xcd, 0x5f, 0x7d, 0x5b, 0x97, 0x0f, 0xbe, 0x06, 0x56, 0xf8, 0x24, 0xc2, 0xec, - 0xaf, 0x69, 0xd5, 0x40, 0x4a, 0x93, 0xdd, 0x21, 0x9a, 0x94, 0x4d, 0xbd, 0xb4, 0xc7, 0x81, 0xfb, - 0x6b, 0x5a, 0x84, 0x47, 0x52, 0xeb, 0xdc, 0x1a, 0xcd, 0xc3, 0xc8, 0xee, 0xa8, 0x45, 0x0d, 0x22, - 0x20, 0x3d, 0xf7, 0xc9, 0x7f, 0xb2, 0x47, 0x28, 0x6d, 0x0d, 0xc7, 0xf1, 0x48, 0x59, 0xcb, 0xcb, - 0xc7, 0x50, 0xad, 0x18, 0xce, 0x9f, 0x17, 0x8d, 0x52, 0xec, 0x1e, 0xe4, 0xbd, 0xf0, 0xc4, 0x8a, - 0xb4, 0xf9, 0xe8, 0x9d, 0x1f, 0x04, 0xb5, 0x9a, 0x5d, 0x9c, 0x29, 0x84, 0x56, 0xff, 0x30, 0x03, - 0x45, 0xd1, 0x03, 0x6c, 0x0b, 0x6a, 0x83, 0xf6, 0x50, 0x7f, 0xd2, 0xd0, 0x3a, 0x8d, 0x9d, 0x6e, - 0x5b, 0xdc, 0xbc, 0xd9, 0xd3, 0x1a, 0x3d, 0x21, 0x20, 0xb5, 0xf6, 0x93, 0xfe, 0xe7, 0x6d, 0xee, - 0xa1, 0x6d, 0xb5, 0x7b, 0x5f, 0x2a, 0x59, 0x7e, 0x1c, 0xd1, 0x3e, 0x6c, 0x68, 0x28, 0x2b, 0x2b, - 0x50, 0x6c, 0x7f, 0xd1, 0x6e, 0x1e, 0x91, 0xb0, 0xdc, 0x00, 0x68, 0xb5, 0x1b, 0xdd, 0x6e, 0xbf, - 0x89, 0xc2, 0xb3, 0xc0, 0x18, 0x6c, 0x34, 0xb5, 0x76, 0x63, 0xd8, 0xd6, 0x1b, 0xcd, 0x66, 0xff, - 0xa8, 0x37, 0x54, 0x8a, 0x58, 0x62, 0xa3, 0x3b, 0x6c, 0x6b, 0x31, 0x88, 0xde, 0x5e, 0x6b, 0x69, - 0xfd, 0xc3, 0x18, 0x52, 0xde, 0x29, 0xa3, 0x0d, 0x48, 0x63, 0xa5, 0xfe, 0xf1, 0x16, 0x6c, 0xa4, - 0xa7, 0x26, 0xfb, 0x18, 0x4a, 0xa6, 0x99, 0x1a, 0xe3, 0x9b, 0xab, 0xa6, 0xf0, 0xc3, 0x96, 0x19, - 0x0d, 0x33, 0xff, 0xc1, 0x5e, 0x8b, 0x16, 0xd2, 0xfa, 0xd2, 0x42, 0x8a, 0x96, 0xd1, 0x0f, 0x60, - 0x53, 0x3c, 0x3e, 0x63, 0x1a, 0xa1, 0x71, 0x6c, 0x04, 0x56, 0x7a, 0x95, 0x34, 0x09, 0xd9, 0x12, - 0xb8, 0xfd, 0x35, 0x6d, 0x63, 0x94, 0x82, 0xb0, 0xef, 0xc3, 0x86, 0xc1, 0xbd, 0x3f, 0x11, 0x7f, - 0x4e, 0x56, 0x43, 0x1b, 0xe4, 0x04, 0x4a, 0xd8, 0x6b, 0x86, 0x0c, 0xc0, 0x89, 0x68, 0xfa, 0xde, - 0x2c, 0x61, 0xce, 0xa7, 0x4e, 0x60, 0x7d, 0x6f, 0x26, 0xf1, 0x56, 0x4d, 0x29, 0xcd, 0x3e, 0x82, - 0xaa, 0xa8, 0x79, 0xe2, 0xea, 0x88, 0x97, 0x2c, 0xaf, 0x36, 0xa9, 0x95, 0xfb, 0x6b, 0x5a, 0x65, - 0x94, 0x24, 0xd9, 0x63, 0xd4, 0x25, 0x13, 0x25, 0xbc, 0x28, 0xcf, 0x35, 0xaa, 0x6d, 0xc4, 0x05, - 0x46, 0x9c, 0x62, 0xef, 0x01, 0x50, 0x3d, 0x39, 0x4f, 0x29, 0x15, 0x9b, 0xe4, 0x7b, 0xb3, 0x88, - 0xa5, 0x6c, 0x46, 0x09, 0xa9, 0x7a, 0xdc, 0x51, 0x55, 0x5e, 0xae, 0x1e, 0x39, 0xab, 0x92, 0xea, - 0x71, 0x1f, 0x57, 0x5c, 0x3d, 0xce, 0x06, 0x4b, 0xd5, 0x8b, 0xb8, 0x78, 0xf5, 0x38, 0x53, 0x54, - 0x3d, 0xce, 0x53, 0x59, 0xac, 0x5e, 0xc4, 0x42, 0xd5, 0xe3, 0x1c, 0xdf, 0x5f, 0xb2, 0x1e, 0xaa, - 0xcf, 0xb5, 0x1e, 0x70, 0xd8, 0xd2, 0xf6, 0xc3, 0xf7, 0x61, 0x23, 0x38, 0xf1, 0xce, 0x24, 0x01, - 0x52, 0x93, 0xb9, 0x07, 0x27, 0xde, 0x99, 0x2c, 0x41, 0x6a, 0x81, 0x0c, 0xc0, 0xda, 0xf2, 0x26, - 0xd2, 0x69, 0xe9, 0x86, 0x5c, 0x5b, 0x6a, 0xe1, 0x13, 0xdb, 0x3a, 0xc3, 0xda, 0x1a, 0x51, 0x02, - 0x3b, 0x25, 0x71, 0xfb, 0x04, 0xc2, 0x91, 0x93, 0x8a, 0xe8, 0x11, 0x25, 0x41, 0xec, 0x00, 0x0a, - 0x70, 0x6e, 0xcd, 0x5d, 0x99, 0x4d, 0x91, 0xe7, 0xd6, 0x91, 0x9b, 0x62, 0xac, 0x72, 0x52, 0xc1, - 0x9a, 0xac, 0x8a, 0xc0, 0xfa, 0x7a, 0x6e, 0xb9, 0x23, 0x4b, 0x44, 0xee, 0xa5, 0x56, 0xc5, 0x40, - 0xe0, 0x92, 0x55, 0x11, 0x41, 0xe2, 0x79, 0x1d, 0xb3, 0xb3, 0xc5, 0x79, 0x2d, 0x31, 0xd3, 0xbc, - 0x8e, 0x59, 0xe3, 0x05, 0x15, 0xf3, 0x5e, 0x5a, 0x5a, 0x50, 0x12, 0x33, 0x5f, 0x50, 0x31, 0xf7, - 0x63, 0x10, 0xb3, 0x89, 0x77, 0x6e, 0x2a, 0xbe, 0x8f, 0xd7, 0x5a, 0xf4, 0x2e, 0x8c, 0xe2, 0x14, - 0xce, 0x55, 0xdf, 0x42, 0x6b, 0x45, 0x4c, 0x85, 0x2b, 0xf2, 0x5c, 0xd5, 0x08, 0x13, 0x2f, 0x25, - 0x3f, 0x49, 0x4a, 0x85, 0xcd, 0xec, 0xd0, 0xaf, 0x9b, 0xcb, 0x85, 0x1d, 0xda, 0xa1, 0x9f, 0x14, - 0x86, 0x29, 0xf6, 0x0e, 0xd0, 0x34, 0xe4, 0x2c, 0x96, 0x2c, 0xba, 0xb1, 0x5b, 0x04, 0x43, 0xc9, - 0x14, 0xbf, 0x71, 0xb2, 0x88, 0x32, 0x46, 0xe6, 0xa8, 0x3e, 0x96, 0x27, 0x0b, 0x2f, 0xa2, 0xd9, - 0x6a, 0xe2, 0x64, 0xe1, 0x44, 0x4d, 0x73, 0xc4, 0x1e, 0x00, 0x71, 0x13, 0xfd, 0x24, 0xf5, 0x8e, - 0x9c, 0xef, 0xcd, 0x38, 0x75, 0x11, 0x09, 0x90, 0x16, 0x5b, 0xe0, 0x78, 0x6e, 0xd4, 0xf0, 0x93, - 0x54, 0x0b, 0x10, 0x11, 0x0b, 0x83, 0x51, 0x9c, 0x52, 0x7f, 0xaf, 0x00, 0x45, 0x21, 0x6b, 0xd9, - 0x25, 0xd8, 0x14, 0x22, 0xbf, 0xd5, 0x18, 0x36, 0x76, 0x1a, 0x03, 0x54, 0xd2, 0x18, 0x6c, 0x70, - 0x99, 0x1f, 0xc3, 0x32, 0xb8, 0x0f, 0x90, 0xd0, 0x8f, 0x41, 0xeb, 0xb8, 0x0f, 0x08, 0x5e, 0xfe, - 0x5e, 0x67, 0x96, 0x6d, 0x42, 0x85, 0x33, 0x72, 0x00, 0x5d, 0x04, 0x26, 0x2e, 0x9e, 0xce, 0x4b, - 0x2c, 0xfc, 0xc0, 0xb2, 0x90, 0xb0, 0x70, 0x40, 0x31, 0x66, 0x89, 0x4e, 0x34, 0x19, 0x6c, 0x0c, - 0xb5, 0xa3, 0x5e, 0x33, 0x29, 0xa7, 0x4c, 0x97, 0x37, 0x79, 0x36, 0x4f, 0x3a, 0xed, 0xa7, 0x0a, - 0x20, 0x13, 0xcf, 0x85, 0xd2, 0x15, 0x54, 0x33, 0x29, 0x13, 0x4a, 0x56, 0xd9, 0x35, 0xb8, 0x34, - 0xd8, 0xef, 0x3f, 0xd5, 0x39, 0x53, 0xdc, 0x84, 0x1a, 0xbb, 0x0c, 0x8a, 0x84, 0xe0, 0xd9, 0x6f, - 0x60, 0x91, 0x04, 0x8d, 0x08, 0x07, 0xca, 0x26, 0x1d, 0xfe, 0x23, 0x6c, 0xc8, 0xf7, 0x5d, 0x05, - 0x9b, 0xc2, 0x59, 0xfb, 0xdd, 0xa3, 0x83, 0xde, 0x40, 0xd9, 0xc2, 0x4a, 0x10, 0x84, 0xd7, 0x9c, - 0xc5, 0xd9, 0x24, 0xbb, 0xf5, 0x25, 0xda, 0xc0, 0x11, 0xf6, 0xb4, 0xa1, 0xf5, 0x3a, 0xbd, 0xbd, - 0x81, 0x72, 0x39, 0xce, 0xb9, 0xad, 0x69, 0x7d, 0x6d, 0xa0, 0x5c, 0x89, 0x01, 0x83, 0x61, 0x63, - 0x78, 0x34, 0x50, 0xae, 0xc6, 0xb5, 0x3c, 0xd4, 0xfa, 0xcd, 0xf6, 0x60, 0xd0, 0xed, 0x0c, 0x86, - 0xca, 0x35, 0x76, 0x05, 0xb6, 0x92, 0x1a, 0x45, 0xc4, 0x75, 0xa9, 0xa2, 0xda, 0x5e, 0x7b, 0xa8, - 0x5c, 0x8f, 0xab, 0xd1, 0xec, 0x77, 0xbb, 0x0d, 0x3a, 0xd9, 0xbe, 0x81, 0x44, 0x74, 0x98, 0x2f, - 0x5a, 0xf3, 0x0a, 0xd6, 0xeb, 0xa8, 0x27, 0x83, 0x6e, 0x4a, 0x53, 0x63, 0xd0, 0xfe, 0xd1, 0x51, - 0xbb, 0xd7, 0x6c, 0x2b, 0xaf, 0x26, 0x53, 0x23, 0x86, 0xdd, 0x8a, 0xa7, 0x46, 0x0c, 0xba, 0x1d, - 0x97, 0x19, 0x81, 0x06, 0xca, 0x36, 0xe6, 0x27, 0xea, 0xd1, 0xeb, 0xb5, 0x9b, 0x43, 0x6c, 0xeb, - 0x6b, 0x71, 0x2f, 0x1e, 0x1d, 0xee, 0x69, 0x8d, 0x56, 0x5b, 0x51, 0x11, 0xa2, 0xb5, 0x7b, 0x8d, - 0x83, 0x68, 0xb4, 0xef, 0x48, 0xa3, 0x7d, 0xd8, 0x19, 0x6a, 0xca, 0xdd, 0x78, 0x74, 0x29, 0xf9, - 0x3a, 0x7b, 0x05, 0xae, 0xc9, 0xf3, 0x50, 0x7f, 0xda, 0x19, 0xee, 0x8b, 0x83, 0xf8, 0x7b, 0xfc, - 0x10, 0x99, 0x90, 0xcd, 0x56, 0x93, 0x47, 0x1c, 0x10, 0x2f, 0xa6, 0xee, 0xef, 0x54, 0xe9, 0x11, - 0x78, 0xa1, 0x80, 0xa8, 0x9f, 0x01, 0x93, 0xdf, 0x38, 0x16, 0x01, 0xd0, 0x0c, 0x72, 0x63, 0xdf, - 0x9b, 0x46, 0x8f, 0x72, 0xe0, 0x6f, 0xb4, 0xbf, 0x67, 0xf3, 0x63, 0x3a, 0xdc, 0x4e, 0x2e, 0xdd, - 0xcb, 0x20, 0xf5, 0x6f, 0x66, 0x60, 0x23, 0xad, 0x7c, 0x90, 0xc3, 0x75, 0xac, 0xbb, 0x5e, 0xc8, - 0x1f, 0x4e, 0x0b, 0xe2, 0xa7, 0x87, 0xc7, 0x3d, 0x2f, 0xa4, 0x97, 0xd3, 0xc8, 0x1d, 0x10, 0xeb, - 0x12, 0x3c, 0xd7, 0x38, 0xcd, 0x3a, 0x70, 0x29, 0xf5, 0xa4, 0x74, 0xea, 0xd9, 0xba, 0x7a, 0xfc, - 0xf8, 0xea, 0x42, 0xfd, 0x35, 0x16, 0x2c, 0xb7, 0x49, 0x3c, 0x9d, 0x90, 0x4b, 0x9e, 0x4e, 0xd8, - 0x87, 0x5a, 0x4a, 0xd7, 0x21, 0x2f, 0xce, 0x38, 0x5d, 0xd3, 0x92, 0x3d, 0x7e, 0x79, 0x35, 0xd5, - 0xbf, 0x9e, 0x81, 0xaa, 0xac, 0xf9, 0x7c, 0xe7, 0x9c, 0x28, 0x2e, 0x49, 0xfc, 0xd6, 0x6d, 0x33, - 0x7a, 0x05, 0x2d, 0x02, 0x75, 0xe8, 0xe3, 0x17, 0xdc, 0x77, 0xbd, 0x7b, 0x3a, 0x88, 0x9b, 0x23, - 0x83, 0xd8, 0x2d, 0x00, 0x7a, 0xa1, 0x68, 0xf7, 0x73, 0x24, 0x10, 0x6f, 0x7f, 0x27, 0x10, 0xf5, - 0x36, 0x94, 0x77, 0x4f, 0xa3, 0x30, 0x2d, 0xf9, 0xf9, 0xc0, 0x32, 0x7f, 0xa9, 0x41, 0xfd, 0xa3, - 0x0c, 0x6c, 0x24, 0x3e, 0x0b, 0x0a, 0x2b, 0xe0, 0x4f, 0x91, 0xf3, 0xe9, 0xb0, 0x6e, 0x1e, 0x27, - 0xe7, 0x9f, 0xeb, 0xf2, 0xf9, 0xe7, 0x1d, 0x91, 0x59, 0x56, 0x16, 0xf9, 0x71, 0x59, 0xe2, 0x1d, - 0x88, 0xc7, 0x50, 0xc5, 0xff, 0x9a, 0x35, 0xb6, 0x7c, 0xdf, 0x32, 0xd3, 0xd7, 0x52, 0x12, 0xe2, - 0x14, 0x11, 0xd9, 0x78, 0xe2, 0x64, 0xf5, 0x39, 0x4f, 0x3c, 0xd1, 0xd3, 0x63, 0xff, 0x39, 0x0b, - 0x15, 0x49, 0x8f, 0xfc, 0x46, 0xd3, 0xef, 0x26, 0x94, 0x93, 0x07, 0x87, 0xc4, 0xe5, 0xfb, 0x18, - 0x90, 0x1a, 0xab, 0xec, 0xc2, 0x58, 0xd5, 0xa1, 0xe8, 0xf3, 0xcb, 0xb2, 0xc2, 0x85, 0x1c, 0x25, - 0xd3, 0xce, 0xda, 0xfc, 0x4b, 0xce, 0x57, 0xde, 0x87, 0xaa, 0xe4, 0x69, 0x0d, 0xea, 0x05, 0xf9, - 0xd5, 0xa9, 0x98, 0xbe, 0x92, 0x78, 0x5d, 0x03, 0x76, 0x05, 0x0a, 0xe3, 0x53, 0xdd, 0x3c, 0xe6, - 0x57, 0xb2, 0xcb, 0x5a, 0x7e, 0x7c, 0xda, 0x3a, 0xa6, 0xd3, 0xa7, 0x71, 0xac, 0x3a, 0x71, 0xff, - 0x57, 0x69, 0x1c, 0x29, 0x48, 0xf7, 0xa1, 0x38, 0x3e, 0x95, 0xaf, 0x56, 0x2f, 0x75, 0x79, 0x61, - 0x7c, 0x4a, 0x17, 0xaa, 0xdf, 0x85, 0xcb, 0x62, 0xff, 0x36, 0x02, 0x9d, 0xbf, 0x73, 0x42, 0x0f, - 0x51, 0xf1, 0xc7, 0x04, 0xb7, 0x38, 0xae, 0x11, 0x0c, 0x08, 0x83, 0x33, 0x4e, 0x85, 0xaa, 0x34, - 0x01, 0xf9, 0x8b, 0x5d, 0x65, 0x2d, 0x05, 0x63, 0x9f, 0x40, 0x75, 0x7c, 0xca, 0x07, 0x74, 0xe8, - 0x1d, 0x58, 0xe2, 0xa6, 0xc8, 0xe5, 0xc5, 0xa1, 0xa4, 0xb8, 0x8f, 0x14, 0x25, 0xbb, 0x0a, 0x05, - 0xcd, 0x38, 0x1b, 0xfc, 0xa8, 0x4b, 0x4a, 0x64, 0x59, 0x13, 0xa9, 0xcf, 0x72, 0xa5, 0x0d, 0x65, - 0x53, 0xfd, 0xfb, 0x19, 0xd8, 0x48, 0x6c, 0x00, 0x5c, 0x84, 0xec, 0x81, 0xfc, 0x0d, 0x81, 0xfa, - 0xa2, 0x99, 0x80, 0x24, 0x0f, 0x87, 0x17, 0x33, 0xfe, 0xb6, 0xed, 0xaa, 0x57, 0xd6, 0x56, 0xb9, - 0xbe, 0xb3, 0x2b, 0x5f, 0x24, 0xdf, 0x83, 0xec, 0xf0, 0x62, 0xc6, 0xfd, 0x4d, 0xb8, 0x25, 0x72, - 0xdb, 0x94, 0x6f, 0x86, 0x14, 0x64, 0xf4, 0x79, 0xfb, 0x4b, 0xfe, 0x58, 0xc8, 0xa1, 0xd6, 0x39, - 0x68, 0x68, 0x5f, 0xea, 0x08, 0x20, 0xa5, 0x61, 0xb7, 0xaf, 0xb5, 0x3b, 0x7b, 0x3d, 0x02, 0xe4, - 0xc8, 0x1b, 0x95, 0x54, 0xb1, 0x61, 0x9a, 0xbb, 0xa7, 0xf2, 0x03, 0x55, 0x99, 0xd4, 0x03, 0x55, - 0xf1, 0x5b, 0x6e, 0xf2, 0x1b, 0x98, 0x61, 0xec, 0x8f, 0x67, 0xf1, 0x2a, 0x8c, 0x97, 0x34, 0x7b, - 0x03, 0x72, 0xe3, 0x53, 0xeb, 0x22, 0x6d, 0xe8, 0xa5, 0x17, 0x10, 0x11, 0xa8, 0xbf, 0xc8, 0x00, - 0x4b, 0x55, 0x84, 0xdb, 0x1e, 0xdf, 0xb5, 0x2e, 0x1f, 0x43, 0x5d, 0x44, 0x62, 0x72, 0x2a, 0xc9, - 0xd7, 0x2e, 0xba, 0xf4, 0x8a, 0x97, 0x44, 0x28, 0x27, 0x0f, 0xc1, 0xb1, 0x77, 0x81, 0x3f, 0x6f, - 0x49, 0x61, 0x42, 0xb9, 0xe7, 0xd8, 0x89, 0x5a, 0x42, 0x93, 0xbc, 0x67, 0x29, 0xbf, 0xd3, 0xc9, - 0xdd, 0xf4, 0x9b, 0xc9, 0xa8, 0xd1, 0x9a, 0x57, 0xff, 0xbf, 0x0c, 0x5c, 0x4a, 0x4f, 0x88, 0x5f, - 0xae, 0x95, 0xe9, 0x47, 0x49, 0xb3, 0x8b, 0x8f, 0x92, 0xae, 0x9a, 0x4f, 0xb9, 0x95, 0xf3, 0xe9, - 0x77, 0x33, 0x70, 0x59, 0xea, 0xfd, 0xc4, 0x5a, 0xfc, 0x0b, 0xaa, 0x99, 0xf4, 0x36, 0x69, 0x2e, - 0xf5, 0x36, 0xa9, 0xfa, 0xf3, 0x0c, 0x5c, 0x5d, 0xa8, 0x89, 0x66, 0xfd, 0x85, 0xd6, 0x25, 0xfd, - 0x86, 0x29, 0x1d, 0x15, 0x44, 0x0f, 0x6a, 0x64, 0xee, 0x67, 0xa5, 0x37, 0x4c, 0xe9, 0xfc, 0x88, - 0x0e, 0x31, 0x5f, 0x15, 0xef, 0x2e, 0xe9, 0xc1, 0x85, 0x3b, 0x12, 0x83, 0x5d, 0x26, 0xc8, 0xe0, - 0xc2, 0x1d, 0xa9, 0x7f, 0x9c, 0x81, 0xeb, 0x0b, 0x6d, 0x68, 0xcc, 0x43, 0x4f, 0x9c, 0x05, 0xff, - 0x05, 0x35, 0xe3, 0x36, 0x54, 0xe8, 0xa4, 0x5c, 0x1c, 0x30, 0xf3, 0x6e, 0x05, 0x23, 0x29, 0x57, - 0x81, 0xac, 0x69, 0x5c, 0x88, 0x47, 0x0b, 0xf0, 0x27, 0x2e, 0xd8, 0x13, 0x6f, 0xee, 0x8b, 0xe8, - 0x20, 0xfa, 0xad, 0x7e, 0x00, 0x5b, 0x49, 0xd5, 0x9b, 0xe2, 0x29, 0xd9, 0xdb, 0x50, 0x71, 0xad, - 0x33, 0x3d, 0x7a, 0x68, 0x56, 0xc4, 0xbc, 0xb9, 0xd6, 0x99, 0x20, 0x50, 0x77, 0x65, 0x59, 0x18, - 0x7f, 0xae, 0xc3, 0x31, 0x53, 0x71, 0x3b, 0x9e, 0x63, 0x46, 0x28, 0xcc, 0x4d, 0x6a, 0x65, 0xd1, - 0xb5, 0xce, 0x68, 0x1e, 0x9e, 0x89, 0x7c, 0x1a, 0xa6, 0x78, 0xd8, 0x75, 0xe5, 0x33, 0x6d, 0xd7, - 0xa1, 0x34, 0xf3, 0x53, 0xdd, 0x54, 0x9c, 0xf9, 0xbc, 0xd8, 0xbb, 0x22, 0xa2, 0xee, 0x79, 0x71, - 0x0e, 0x3c, 0xc6, 0x4e, 0x7c, 0xce, 0x27, 0x97, 0x7c, 0xce, 0xe7, 0x43, 0x21, 0x06, 0xc9, 0xee, - 0xe3, 0x25, 0x2b, 0x90, 0xb5, 0xcd, 0x73, 0x2a, 0xb8, 0xa6, 0xe1, 0x4f, 0xd2, 0xe4, 0xac, 0xaf, - 0x45, 0x50, 0x1f, 0xfe, 0x54, 0x77, 0xa0, 0xa2, 0xa5, 0x8c, 0xdc, 0xaa, 0xe4, 0x2f, 0x0a, 0xd2, - 0x2f, 0x59, 0x25, 0x1d, 0xa4, 0x55, 0x12, 0x77, 0x51, 0xa0, 0x06, 0x42, 0xf0, 0x3d, 0x31, 0xfc, - 0xd1, 0x89, 0xe1, 0x77, 0x2d, 0x77, 0x12, 0x9e, 0x60, 0x97, 0x73, 0x37, 0xae, 0xdc, 0x85, 0xc0, - 0x41, 0xd1, 0x74, 0xc0, 0x5e, 0x74, 0x88, 0x3c, 0xfa, 0xf8, 0x87, 0x6b, 0x9d, 0x09, 0xfe, 0x57, - 0x01, 0xb0, 0xff, 0x05, 0x9a, 0x1f, 0x41, 0x96, 0x3d, 0xc7, 0xe4, 0x68, 0x75, 0x4b, 0xb4, 0x57, - 0x3c, 0xba, 0xd1, 0xb2, 0xc6, 0xaa, 0x23, 0x46, 0x9e, 0x37, 0x48, 0x74, 0xc2, 0x77, 0x1a, 0x46, - 0xf6, 0x1a, 0x54, 0x23, 0x8f, 0x04, 0x3d, 0x9e, 0xc6, 0x8b, 0xaf, 0x44, 0xb0, 0xde, 0x7c, 0xaa, - 0xfe, 0x41, 0x16, 0xaa, 0x0d, 0x1e, 0xd9, 0x33, 0xbb, 0xe8, 0xcf, 0x42, 0xf6, 0x5b, 0x70, 0x85, - 0x5e, 0x53, 0xe1, 0x4f, 0x24, 0x53, 0x40, 0x0d, 0x85, 0xe7, 0x8b, 0x4e, 0x7c, 0x20, 0x75, 0xa2, - 0x60, 0x79, 0x38, 0x38, 0xb5, 0x67, 0xfc, 0x56, 0x48, 0xc7, 0x3c, 0xa7, 0x2b, 0x18, 0x3c, 0x36, - 0x80, 0x1e, 0x5e, 0x49, 0x23, 0xe8, 0xed, 0x05, 0xcc, 0x7e, 0x76, 0x2a, 0xb2, 0x15, 0x61, 0x13, - 0x08, 0x3c, 0x3c, 0xe5, 0x34, 0x0f, 0x60, 0x8b, 0x5f, 0x04, 0x5b, 0xde, 0x80, 0x37, 0x39, 0x22, - 0x99, 0xdf, 0x03, 0xd8, 0x12, 0x8f, 0xbf, 0xd0, 0x6b, 0xa0, 0xfa, 0xc8, 0x9b, 0x5d, 0x88, 0xa3, - 0xc7, 0x37, 0x9e, 0x53, 0xd5, 0x0e, 0x27, 0x45, 0x10, 0xaf, 0xe7, 0x66, 0x90, 0x86, 0xde, 0x68, - 0xc3, 0xb5, 0xe7, 0xb4, 0xe9, 0x65, 0xe1, 0x0d, 0x25, 0x29, 0xbc, 0xe1, 0xc6, 0x0e, 0x5c, 0x5e, - 0x55, 0xde, 0xb7, 0xc9, 0x43, 0xfd, 0x59, 0x0d, 0x20, 0x99, 0xb1, 0x29, 0x75, 0x34, 0xb3, 0xa0, - 0x8e, 0x7e, 0xab, 0xa0, 0x9e, 0x0f, 0x60, 0x03, 0xbb, 0x4a, 0x4f, 0x38, 0xb2, 0x2b, 0x39, 0xaa, - 0x48, 0x35, 0x4c, 0xee, 0xba, 0x2e, 0x87, 0x44, 0xe4, 0x56, 0x86, 0x44, 0xbc, 0x0f, 0x45, 0x7e, - 0xd0, 0x16, 0x88, 0xeb, 0xd5, 0xd7, 0x16, 0x57, 0xdf, 0x43, 0x71, 0xc3, 0x24, 0xa2, 0x63, 0x6d, - 0xd8, 0x40, 0xd1, 0xef, 0xdb, 0xe1, 0xc9, 0x54, 0xbe, 0x6c, 0x7d, 0x6b, 0x99, 0x33, 0x22, 0x13, - 0x5f, 0x5c, 0x91, 0x93, 0x92, 0xf6, 0x1a, 0x4e, 0x85, 0xf7, 0x97, 0xb4, 0xd7, 0xa2, 0xac, 0xbd, - 0x0e, 0xa7, 0xdc, 0xe7, 0x8b, 0xda, 0xeb, 0x3b, 0x70, 0x49, 0xdc, 0x7a, 0x43, 0x06, 0xec, 0x4e, - 0xa2, 0xe7, 0x41, 0x96, 0x8a, 0x78, 0x73, 0x65, 0x4a, 0xb6, 0x1d, 0x92, 0x7f, 0x01, 0x97, 0x47, - 0x27, 0x86, 0x3b, 0xb1, 0xf4, 0xf0, 0xd8, 0xd1, 0xe9, 0xbb, 0x0c, 0xfa, 0xd4, 0x98, 0x09, 0xa5, - 0xfa, 0x8d, 0xa5, 0xca, 0x36, 0x89, 0x78, 0x78, 0xec, 0x50, 0x00, 0x5a, 0x1c, 0x38, 0xb3, 0x35, - 0x5a, 0x84, 0x2f, 0x9c, 0x5f, 0xc3, 0xd2, 0xf9, 0xf5, 0xa2, 0x9a, 0x5d, 0x59, 0xa1, 0x66, 0x27, - 0xca, 0x72, 0x55, 0x56, 0x96, 0xd9, 0xdb, 0x50, 0x14, 0x97, 0x76, 0x85, 0xdf, 0x97, 0x2d, 0xaf, - 0x0e, 0x2d, 0x22, 0xc1, 0x92, 0xa2, 0x68, 0x0a, 0x7a, 0xd7, 0x61, 0x83, 0x97, 0x24, 0xc3, 0xd8, - 0x8e, 0x70, 0x7a, 0xc6, 0xc1, 0x72, 0xc2, 0xc7, 0x7b, 0x43, 0xca, 0x38, 0xc6, 0x09, 0xbb, 0x7c, - 0x81, 0xe3, 0xc6, 0xdf, 0x2b, 0x40, 0x41, 0x04, 0xca, 0x3f, 0x80, 0x9c, 0xe9, 0x7b, 0xb3, 0x38, - 0xda, 0x7c, 0x85, 0xd6, 0x4e, 0x1f, 0x06, 0x44, 0x05, 0xff, 0x21, 0x14, 0x0c, 0xd3, 0xd4, 0xc7, - 0xa7, 0xe9, 0xf3, 0xe8, 0x05, 0x05, 0x7a, 0x7f, 0x4d, 0xcb, 0x1b, 0xa4, 0x49, 0x7f, 0x0c, 0x65, - 0xa4, 0x4f, 0xc2, 0x4f, 0x2b, 0xcb, 0x66, 0x41, 0xa4, 0xea, 0xee, 0xaf, 0x69, 0x25, 0x23, 0x52, - 0x7b, 0x7f, 0x3d, 0xed, 0xd9, 0xcf, 0x2d, 0x35, 0x70, 0x41, 0x4f, 0x5b, 0xf0, 0xf1, 0xff, 0x06, - 0x70, 0x57, 0x6f, 0xbc, 0x63, 0xe7, 0xe5, 0xa3, 0xcf, 0xa5, 0xfd, 0x7d, 0x7f, 0x4d, 0xe3, 0xfb, - 0x56, 0xb4, 0xdf, 0x7f, 0x18, 0x79, 0xdd, 0xe3, 0x8f, 0x22, 0xad, 0xe8, 0x19, 0x14, 0x83, 0xb1, - 0xeb, 0x9d, 0x64, 0x22, 0xb2, 0x99, 0xd1, 0x4b, 0xed, 0xe2, 0xb4, 0x44, 0x66, 0x8b, 0x77, 0x75, - 0x62, 0x8b, 0xb7, 0xf8, 0x4f, 0xa0, 0xc2, 0x9d, 0xb0, 0x9c, 0xaf, 0xb4, 0xd4, 0xb5, 0xc9, 0xa6, - 0x4c, 0xc7, 0x7a, 0xc9, 0x16, 0xdd, 0x8c, 0xda, 0xe9, 0x5b, 0xf2, 0xc9, 0xc9, 0xcd, 0x95, 0x1d, - 0xa5, 0xc5, 0x87, 0x28, 0xbc, 0xb1, 0x1a, 0xe7, 0x61, 0x5d, 0xb8, 0x2c, 0x8e, 0x18, 0xf8, 0x06, - 0x1c, 0xed, 0x99, 0xb0, 0x34, 0x5e, 0xa9, 0x1d, 0x7a, 0x7f, 0x4d, 0x63, 0xc6, 0xf2, 0xbe, 0xdd, - 0x84, 0xad, 0xa8, 0x4a, 0xb4, 0xb3, 0x4a, 0x61, 0x53, 0x72, 0x93, 0x92, 0x7d, 0x77, 0x7f, 0x4d, - 0xdb, 0x34, 0xd2, 0x20, 0xd6, 0x81, 0x4b, 0x51, 0x26, 0xe4, 0x6a, 0x17, 0x3d, 0x53, 0x5d, 0x1a, - 0x45, 0x79, 0xaf, 0xde, 0x5f, 0xd3, 0xb6, 0x8c, 0xa5, 0x0d, 0xfc, 0x20, 0xaa, 0x8f, 0xac, 0x1c, - 0xf2, 0x95, 0x78, 0x7b, 0x65, 0x37, 0x25, 0x9a, 0x6a, 0x5c, 0xb3, 0x04, 0x94, 0xc4, 0x31, 0xdc, - 0xd0, 0xe0, 0xea, 0x6a, 0x09, 0x23, 0x6f, 0x33, 0x39, 0xbe, 0xcd, 0xa8, 0xf2, 0x36, 0xb3, 0xf8, - 0x4e, 0x8b, 0xb4, 0xe9, 0xfc, 0x10, 0x6a, 0x29, 0x11, 0xcb, 0x2a, 0x50, 0x8c, 0x5e, 0xa9, 0xa7, - 0xcb, 0x31, 0xcd, 0xfe, 0xe1, 0x97, 0x4a, 0x06, 0xc1, 0x9d, 0xde, 0x60, 0xd8, 0xe8, 0x0d, 0x95, - 0x75, 0x9e, 0x38, 0xec, 0x36, 0x9a, 0x6d, 0x25, 0xab, 0xfe, 0x3c, 0x0b, 0xe5, 0xf8, 0x94, 0xed, - 0xbb, 0x7b, 0xc3, 0x62, 0x37, 0x53, 0x56, 0x76, 0x33, 0x2d, 0x98, 0x7a, 0xfc, 0xdb, 0x13, 0xb9, - 0xe8, 0x7b, 0x04, 0xb2, 0x41, 0x15, 0x2c, 0x5f, 0xdf, 0xcf, 0x7f, 0xc3, 0xeb, 0xfb, 0x72, 0x04, - 0x7a, 0x21, 0x1d, 0x81, 0xbe, 0xf0, 0xa5, 0x82, 0x22, 0xbd, 0x21, 0x2e, 0x7f, 0xa9, 0x80, 0x3e, - 0xd9, 0xfa, 0xc4, 0xb6, 0xce, 0x44, 0xc8, 0xb6, 0x48, 0xa5, 0x77, 0x68, 0x78, 0xc9, 0x0e, 0xfd, - 0x4d, 0xa4, 0xfd, 0x23, 0xb8, 0x3c, 0x3e, 0x8d, 0x5f, 0x2e, 0x4f, 0x9c, 0x2b, 0x55, 0xaa, 0xd2, - 0x4a, 0x1c, 0x7b, 0x23, 0xfe, 0x6e, 0x5c, 0x4d, 0x76, 0x03, 0xc5, 0xa3, 0x15, 0x7f, 0x48, 0xee, - 0xff, 0xc9, 0x00, 0x24, 0xe7, 0x4f, 0xbf, 0xb4, 0x2b, 0x57, 0xf2, 0x96, 0x65, 0x5f, 0xe0, 0x2d, - 0x7b, 0xd9, 0x0b, 0x75, 0x5f, 0x43, 0x39, 0x3e, 0x71, 0xfc, 0xee, 0x13, 0xeb, 0x5b, 0x15, 0xf9, - 0xdb, 0x91, 0x5b, 0x3b, 0x3e, 0xb2, 0xfb, 0x65, 0xfb, 0x22, 0x55, 0x7c, 0xf6, 0x25, 0xc5, 0x9f, - 0x73, 0xdf, 0x72, 0x5c, 0xf8, 0xaf, 0x78, 0x35, 0xc9, 0x13, 0x3d, 0x97, 0x9a, 0xe8, 0xea, 0x5c, - 0x38, 0xc8, 0x7f, 0xf9, 0xa2, 0xbf, 0x55, 0x83, 0xff, 0x53, 0x26, 0xf2, 0xe2, 0xc6, 0x8f, 0xcd, - 0x3f, 0x57, 0xe9, 0x5d, 0xed, 0x88, 0xfe, 0x36, 0xc5, 0xbd, 0xd0, 0x47, 0x95, 0x7b, 0x91, 0x8f, - 0xea, 0x0d, 0xc8, 0xf3, 0xed, 0x2e, 0xff, 0x3c, 0xff, 0x14, 0xc7, 0xbf, 0xf4, 0xeb, 0x31, 0xaa, - 0x2a, 0x94, 0x7c, 0xde, 0xde, 0xcb, 0x51, 0xbe, 0xd1, 0x97, 0x6f, 0xe8, 0x86, 0xcc, 0xff, 0xc1, - 0x25, 0xea, 0x77, 0xed, 0x92, 0x17, 0xbb, 0x2d, 0xd4, 0xff, 0x99, 0x81, 0x5a, 0x2a, 0x82, 0xe0, - 0x3b, 0x14, 0xb1, 0x52, 0x2e, 0x67, 0xff, 0x37, 0x92, 0xcb, 0xa9, 0x10, 0xde, 0x52, 0x3a, 0x84, - 0x17, 0xc5, 0x5d, 0x35, 0x65, 0xc2, 0xac, 0x32, 0x76, 0x32, 0x2b, 0x8d, 0x9d, 0x5b, 0xf1, 0x07, - 0x40, 0x3b, 0x2d, 0x1e, 0x31, 0x5b, 0xd3, 0x24, 0x08, 0xfb, 0x14, 0xae, 0x0b, 0x27, 0x02, 0xef, - 0x1f, 0x6f, 0xac, 0xc7, 0x9f, 0x07, 0x15, 0x46, 0xf9, 0x55, 0x4e, 0xc0, 0xbf, 0xfd, 0x33, 0x6e, - 0x44, 0x58, 0xb5, 0x03, 0xb5, 0x54, 0x68, 0x86, 0xf4, 0xe9, 0xe2, 0x8c, 0xfc, 0xe9, 0x62, 0xb6, - 0x0d, 0xf9, 0xb3, 0x13, 0xcb, 0xb7, 0x56, 0x3c, 0x10, 0xcd, 0x11, 0xea, 0xf7, 0xa1, 0x2a, 0x87, - 0x89, 0xb1, 0xb7, 0x21, 0x6f, 0x87, 0xd6, 0x34, 0x72, 0x8f, 0x5c, 0x5d, 0x8e, 0x24, 0xeb, 0x84, - 0xd6, 0x54, 0xe3, 0x44, 0xea, 0x1f, 0x66, 0x40, 0x59, 0xc4, 0x49, 0xdf, 0x57, 0xce, 0x3c, 0xe7, - 0xfb, 0xca, 0xeb, 0xa9, 0x4a, 0xae, 0xfa, 0x44, 0xf2, 0x76, 0xa4, 0x94, 0xac, 0xf8, 0x3c, 0x2f, - 0x21, 0xd8, 0x3d, 0x28, 0xf9, 0x16, 0x7d, 0xbc, 0xd6, 0x5c, 0x71, 0x73, 0x24, 0xc6, 0xa9, 0xbf, - 0x9f, 0x81, 0xa2, 0x88, 0x69, 0x5b, 0xe9, 0xaf, 0x7a, 0x13, 0x8a, 0xfc, 0x43, 0xb6, 0xd1, 0x43, - 0x75, 0x4b, 0x61, 0xe6, 0x11, 0x9e, 0xdd, 0xe2, 0x91, 0x7e, 0x69, 0xff, 0xd5, 0xa1, 0x63, 0xb8, - 0x1a, 0xc1, 0xc5, 0x27, 0xbd, 0x8c, 0xa9, 0x78, 0xd9, 0x80, 0xbf, 0x30, 0x06, 0x04, 0xa2, 0x47, - 0x0c, 0xd4, 0x5f, 0x87, 0xa2, 0x88, 0x99, 0x5b, 0x59, 0x95, 0x97, 0x7c, 0x98, 0x54, 0xdd, 0x06, - 0x48, 0x82, 0xe8, 0x56, 0xe5, 0xa0, 0x3e, 0x80, 0x52, 0x14, 0x37, 0x87, 0xf3, 0x2f, 0x29, 0x5a, - 0x5c, 0x49, 0x92, 0x2b, 0xe3, 0x88, 0x8f, 0x2e, 0x74, 0xbd, 0xd1, 0x29, 0x39, 0xcb, 0xdf, 0x05, - 0xba, 0x9f, 0x35, 0x5c, 0x7a, 0x8a, 0x2d, 0xfd, 0x75, 0x8e, 0x98, 0x88, 0x3d, 0x80, 0x58, 0x5e, - 0xbe, 0xcc, 0xb5, 0xa0, 0x36, 0xa2, 0x9b, 0x7c, 0x34, 0xcb, 0x1e, 0x0b, 0x6f, 0x6a, 0x97, 0x9e, - 0x02, 0xcd, 0xc8, 0x8f, 0x23, 0xa7, 0xea, 0xa4, 0x49, 0x64, 0xea, 0x06, 0x54, 0xe5, 0x60, 0x1f, - 0xb5, 0x01, 0x5b, 0x07, 0x56, 0x68, 0xa0, 0xfc, 0x89, 0x1e, 0xa8, 0xe2, 0xf3, 0x17, 0x7f, 0xa4, - 0xe7, 0xef, 0x22, 0x9d, 0xc6, 0x89, 0xd4, 0x9f, 0xe7, 0x40, 0x59, 0xc4, 0xbd, 0xe8, 0x56, 0xe3, - 0x6d, 0xa8, 0x78, 0x34, 0x2f, 0x52, 0x1f, 0x6d, 0xe3, 0x20, 0xe9, 0x3e, 0x40, 0xea, 0x73, 0x3c, - 0x25, 0x3b, 0xd8, 0xe7, 0x1f, 0xe4, 0xb9, 0xc6, 0xaf, 0xb0, 0x39, 0xde, 0x88, 0xa6, 0x75, 0x95, - 0x6e, 0xac, 0x75, 0xbd, 0x11, 0x5d, 0x96, 0x14, 0xde, 0x09, 0x1e, 0x81, 0x5a, 0xd5, 0x4a, 0xc2, - 0x25, 0x41, 0xe7, 0x77, 0xe2, 0x96, 0x40, 0x18, 0x88, 0x3b, 0xc0, 0x25, 0x0e, 0x18, 0x06, 0xd1, - 0xd3, 0xfe, 0x23, 0xf1, 0x85, 0xb1, 0x2c, 0x3d, 0xed, 0xdf, 0x74, 0xe9, 0xae, 0x24, 0x3d, 0x78, - 0x3c, 0x12, 0xdf, 0x56, 0x14, 0x1f, 0x57, 0x40, 0xd4, 0x1d, 0xfe, 0x0d, 0x36, 0xdf, 0x0a, 0x02, - 0xfe, 0x22, 0x64, 0x59, 0x3c, 0x05, 0x2a, 0x80, 0xf1, 0x83, 0xbb, 0xe2, 0xf3, 0x78, 0x48, 0x02, - 0xe2, 0x5d, 0x4a, 0xfe, 0x71, 0x3c, 0x24, 0xb8, 0x0e, 0xa5, 0x9f, 0x78, 0xae, 0x45, 0x5e, 0x8e, - 0x0a, 0xd5, 0xaa, 0x88, 0xe9, 0x03, 0x63, 0x86, 0x1b, 0x81, 0x43, 0xd7, 0xcd, 0xf9, 0x2d, 0x41, - 0x9e, 0x50, 0xff, 0x45, 0x06, 0x2e, 0x2f, 0xf6, 0x35, 0x4d, 0xa3, 0x2a, 0x94, 0x9a, 0xfd, 0xae, - 0xde, 0x6b, 0x1c, 0xb4, 0x95, 0x35, 0xb6, 0x09, 0x95, 0xfe, 0xce, 0x67, 0xed, 0xe6, 0x90, 0x03, - 0x32, 0xf4, 0x86, 0xc3, 0x40, 0xdf, 0xef, 0xb4, 0x5a, 0xed, 0x1e, 0xb7, 0x28, 0xfa, 0x3b, 0x9f, - 0xe9, 0xdd, 0x7e, 0x93, 0x7f, 0x1b, 0x2b, 0x8a, 0x78, 0x18, 0x28, 0x39, 0x8a, 0x87, 0xa0, 0x60, - 0x78, 0x4c, 0xe6, 0x79, 0x94, 0xf7, 0xd3, 0x81, 0xde, 0xec, 0x0d, 0x95, 0x02, 0xa6, 0x7a, 0x47, - 0xdd, 0x2e, 0xa5, 0x28, 0x9c, 0xb3, 0xd9, 0x3f, 0x38, 0xd4, 0xda, 0x83, 0x81, 0x3e, 0xe8, 0xfc, - 0xb8, 0xad, 0x94, 0xa8, 0x64, 0xad, 0xb3, 0xd7, 0xe9, 0x71, 0x40, 0x99, 0x15, 0x21, 0x7b, 0xd0, - 0xe9, 0xf1, 0xb7, 0x2b, 0x0e, 0x1a, 0x5f, 0x28, 0x15, 0xfc, 0x31, 0x38, 0x3a, 0x50, 0xaa, 0xac, - 0x0c, 0xf9, 0x6e, 0xfb, 0x49, 0xbb, 0xab, 0xd4, 0xd4, 0x7f, 0x93, 0x8d, 0x34, 0x62, 0x8a, 0x73, - 0xfa, 0x26, 0x5a, 0xe0, 0xaa, 0xf3, 0xc5, 0xb8, 0xd3, 0xb2, 0x52, 0xa7, 0x7d, 0x93, 0xaf, 0x41, - 0xdf, 0x81, 0x5a, 0x1c, 0x1c, 0x20, 0x3d, 0xc7, 0x5f, 0x8d, 0x80, 0x2b, 0x8e, 0x2f, 0x0a, 0x2b, - 0x8e, 0x2f, 0x66, 0x76, 0x88, 0x56, 0x36, 0xca, 0x5c, 0x3e, 0x93, 0xca, 0x08, 0xe1, 0xdf, 0x6c, - 0x7f, 0x05, 0x28, 0xa1, 0xcf, 0x5d, 0x3b, 0xfa, 0x52, 0x67, 0x09, 0x01, 0x47, 0xae, 0x1d, 0x2e, - 0x06, 0x27, 0x94, 0x97, 0x82, 0x13, 0xe4, 0xcd, 0x19, 0xd2, 0x9b, 0x73, 0xfa, 0x23, 0xd9, 0xfc, - 0x13, 0x9d, 0xd2, 0x47, 0xb2, 0xdf, 0x06, 0x36, 0x9a, 0xfb, 0xf4, 0x38, 0x9e, 0x44, 0x56, 0x25, - 0x32, 0x45, 0x60, 0xe2, 0x5d, 0x91, 0xbd, 0x01, 0x9b, 0x0b, 0xd4, 0x64, 0x4b, 0x97, 0xb5, 0x8d, - 0x34, 0x29, 0x7b, 0x08, 0x97, 0xc4, 0xdc, 0x4e, 0xf5, 0xad, 0xb8, 0x7a, 0xca, 0x51, 0x8d, 0xa4, - 0x87, 0xd5, 0x5f, 0x83, 0x52, 0x14, 0xd2, 0xf6, 0x62, 0x65, 0x77, 0xc5, 0xb8, 0xaa, 0x7f, 0x67, - 0x1d, 0xca, 0x71, 0x80, 0xdb, 0x37, 0x9a, 0x1d, 0xf4, 0xdd, 0x91, 0xe0, 0x54, 0x16, 0x31, 0x25, - 0x04, 0x44, 0x23, 0x25, 0x6e, 0x6b, 0xcd, 0x7d, 0x3b, 0xd2, 0xd8, 0x38, 0xe4, 0xc8, 0xb7, 0xe9, - 0x05, 0x1c, 0xdb, 0x95, 0x2e, 0x89, 0x96, 0xb5, 0x12, 0x02, 0x68, 0xa1, 0x5d, 0x07, 0xfa, 0x4d, - 0x9c, 0xd1, 0x77, 0xc3, 0x6d, 0xf7, 0x14, 0xf9, 0x9e, 0xf3, 0xdd, 0x70, 0xfa, 0x6a, 0x0a, 0x8f, - 0xae, 0xe1, 0x31, 0x05, 0xd1, 0xa7, 0x07, 0x5f, 0x81, 0xf2, 0x3c, 0xfe, 0x76, 0xa5, 0x98, 0x11, - 0xf3, 0xe8, 0xcb, 0x95, 0xe9, 0x51, 0x2d, 0x2f, 0x8e, 0xea, 0xe2, 0x9c, 0x86, 0xa5, 0x39, 0xad, - 0x86, 0x50, 0x14, 0x41, 0x7e, 0x2f, 0xee, 0xf0, 0x17, 0x76, 0x95, 0x02, 0x59, 0xc3, 0x89, 0x6e, - 0xa6, 0xe2, 0xcf, 0x85, 0x8a, 0xe5, 0x16, 0x2a, 0xa6, 0xfe, 0xb5, 0x75, 0x80, 0x24, 0x58, 0x90, - 0xbd, 0xb3, 0x10, 0x98, 0x9c, 0x59, 0xda, 0xf6, 0x17, 0xe2, 0x91, 0x17, 0x9e, 0x84, 0x5a, 0xff, - 0x06, 0x4f, 0x42, 0x3d, 0x82, 0x5a, 0xe0, 0x8f, 0x5e, 0xea, 0x70, 0xaf, 0x04, 0xfe, 0x28, 0xf6, - 0xb7, 0xbf, 0x0b, 0x98, 0xa4, 0xe7, 0x22, 0x13, 0x43, 0x75, 0x49, 0x6b, 0x29, 0x07, 0xfe, 0xa8, - 0x7f, 0xfc, 0x55, 0x8b, 0x5f, 0x91, 0x33, 0x83, 0x50, 0x5f, 0x25, 0x25, 0x36, 0xcd, 0x20, 0x6c, - 0xc9, 0x82, 0xe2, 0x2e, 0x6c, 0x20, 0xed, 0x92, 0xb0, 0xa8, 0x9a, 0x41, 0x72, 0xc0, 0xa2, 0xfe, - 0x5e, 0x74, 0x24, 0xbd, 0xe0, 0xcb, 0x65, 0x1f, 0x09, 0x43, 0x5c, 0x52, 0x22, 0xea, 0xab, 0x5c, - 0xbf, 0xfc, 0x01, 0xab, 0x98, 0x74, 0xf9, 0x33, 0x84, 0xeb, 0xdf, 0xf4, 0x33, 0x84, 0xdb, 0x00, - 0xc9, 0x83, 0x9d, 0xb8, 0x02, 0xe3, 0xcb, 0x3a, 0x65, 0x7e, 0x0d, 0xe7, 0xc1, 0x01, 0x5c, 0x5a, - 0xf1, 0x4d, 0x76, 0x76, 0x09, 0x36, 0x1b, 0x7b, 0x7b, 0x7a, 0xb3, 0xdf, 0xdb, 0xed, 0xec, 0xe9, - 0xe2, 0x5a, 0xce, 0x6b, 0xf0, 0xaa, 0x04, 0xdc, 0xd3, 0xfa, 0x47, 0x87, 0x98, 0x68, 0x36, 0x86, - 0xe2, 0xab, 0xe8, 0x99, 0x07, 0xaf, 0x41, 0x55, 0xfe, 0x62, 0x32, 0xdd, 0xe9, 0xf1, 0x5c, 0x8b, - 0x7f, 0x11, 0xa6, 0xfb, 0x93, 0x0f, 0x94, 0xcc, 0x03, 0x15, 0x2a, 0xd2, 0xf7, 0x98, 0x90, 0x62, - 0xdf, 0x08, 0x4e, 0xc4, 0xd7, 0x41, 0x0c, 0x77, 0x62, 0x29, 0x99, 0x07, 0xf7, 0x50, 0x87, 0x97, - 0xbf, 0x86, 0x04, 0x50, 0xe8, 0x79, 0xfe, 0xd4, 0x70, 0x04, 0x9d, 0x35, 0x0f, 0x90, 0xee, 0x5d, - 0xb8, 0xb2, 0xf2, 0xdb, 0x4e, 0x74, 0x23, 0xcc, 0x9e, 0xce, 0x1c, 0x8b, 0xdf, 0x6d, 0xda, 0xbf, - 0x38, 0xf6, 0x6d, 0x53, 0xc9, 0x3c, 0xf8, 0x64, 0xe1, 0xdb, 0x1f, 0x47, 0xbd, 0x9d, 0xfe, 0x51, - 0xaf, 0xd5, 0x6e, 0xf1, 0x5b, 0x47, 0x9d, 0x5e, 0xb3, 0x7b, 0x34, 0xe8, 0x3c, 0x11, 0x5b, 0x6b, - 0xfb, 0x8b, 0x28, 0xb9, 0xfe, 0x60, 0x3f, 0x7a, 0xdc, 0x21, 0xaa, 0x75, 0xb7, 0xdf, 0x68, 0xf1, - 0x2d, 0x39, 0x7e, 0xde, 0x69, 0xb8, 0xc3, 0xbf, 0x19, 0xa2, 0xb5, 0x07, 0x47, 0xdd, 0x61, 0xf4, - 0x94, 0xd4, 0x06, 0x40, 0xa7, 0xd9, 0xde, 0x69, 0x6b, 0x7b, 0x48, 0x90, 0x7d, 0xf0, 0x43, 0xa8, - 0x3f, 0xef, 0xba, 0x10, 0xb6, 0xad, 0xb9, 0xdf, 0xa0, 0x2b, 0x59, 0xb8, 0x25, 0xf7, 0x75, 0x9e, - 0x22, 0xc7, 0xa1, 0xd6, 0xee, 0xb6, 0x29, 0xa6, 0xf6, 0xc1, 0x4f, 0x33, 0x92, 0x7a, 0x1a, 0x5d, - 0xf9, 0x88, 0x01, 0xa2, 0xc3, 0x65, 0x90, 0x66, 0x19, 0xa6, 0x92, 0x61, 0x57, 0x81, 0xa5, 0x40, - 0x5d, 0x6f, 0x64, 0x38, 0xca, 0x3a, 0x45, 0xcf, 0x46, 0x70, 0xba, 0x1a, 0xa8, 0x64, 0xd9, 0xab, - 0x70, 0x3d, 0x86, 0x75, 0xbd, 0xb3, 0x43, 0xdf, 0xf6, 0x7c, 0x3b, 0xbc, 0xe0, 0xe8, 0xdc, 0x83, - 0xff, 0x53, 0x9c, 0xfd, 0xa6, 0x26, 0x2d, 0x16, 0xd0, 0x30, 0xcd, 0x04, 0x46, 0x72, 0x52, 0x59, - 0x63, 0xd7, 0xe0, 0x12, 0x6d, 0x12, 0x0b, 0x88, 0x0c, 0x7b, 0x05, 0xae, 0x45, 0x36, 0xf4, 0x22, - 0x72, 0x1d, 0x91, 0x9a, 0x45, 0x91, 0x97, 0x4b, 0xc8, 0xec, 0xce, 0x0f, 0xfe, 0xe4, 0x17, 0xb7, - 0x32, 0xff, 0xf4, 0x17, 0xb7, 0x32, 0xff, 0xf6, 0x17, 0xb7, 0xd6, 0xfe, 0xf0, 0xdf, 0xdf, 0xca, - 0xfc, 0xf8, 0x9d, 0x89, 0x1d, 0x9e, 0xcc, 0x8f, 0x1f, 0x8e, 0xbc, 0xe9, 0xbb, 0x53, 0x23, 0xf4, - 0xed, 0x73, 0xbe, 0x5b, 0x45, 0x09, 0xd7, 0x7a, 0x77, 0x76, 0x3a, 0x79, 0x77, 0x76, 0xfc, 0x2e, - 0xae, 0x9b, 0xe3, 0xc2, 0xcc, 0xf7, 0x42, 0xef, 0xf1, 0xff, 0x0a, 0x00, 0x00, 0xff, 0xff, 0x31, - 0xcf, 0x28, 0x81, 0x93, 0x8b, 0x00, 0x00, + 0xa4, 0xd3, 0x7b, 0xdc, 0x6d, 0x2b, 0x05, 0x84, 0x1e, 0x34, 0xb4, 0xcf, 0x95, 0x22, 0xcf, 0xb4, + 0xd5, 0xfe, 0x42, 0x29, 0xb1, 0x02, 0xac, 0x77, 0x1f, 0x2a, 0x65, 0x04, 0xb5, 0xda, 0xad, 0xa3, + 0x43, 0x05, 0xd4, 0x7b, 0x50, 0x6c, 0x4c, 0x26, 0x07, 0x9e, 0x49, 0x27, 0xbc, 0x7b, 0x47, 0xdd, + 0x2e, 0x5f, 0x30, 0xbb, 0xfd, 0xe1, 0xb0, 0x7f, 0xa0, 0x64, 0x70, 0x8a, 0x0e, 0xfb, 0x87, 0xca, + 0xba, 0xda, 0x81, 0x52, 0xa4, 0x01, 0x48, 0x17, 0xfa, 0x4a, 0x90, 0x3b, 0xd4, 0xda, 0x4f, 0x78, + 0x8c, 0x40, 0xaf, 0xfd, 0x05, 0x56, 0x13, 0x7f, 0x61, 0x46, 0x59, 0x2c, 0x88, 0xdf, 0xbc, 0xa3, + 0x1b, 0x7d, 0xdd, 0x4e, 0xaf, 0xdd, 0xd0, 0x94, 0xbc, 0xfa, 0x21, 0x6c, 0x2d, 0xc9, 0x4f, 0x2a, + 0xbe, 0xd1, 0x11, 0xc5, 0x77, 0x1e, 0xf7, 0xfa, 0x5a, 0x9b, 0x5f, 0x11, 0x14, 0x9d, 0xba, 0xae, + 0xbe, 0x05, 0xe5, 0x58, 0xd0, 0xe3, 0x24, 0x6b, 0x6a, 0xfd, 0xc1, 0x80, 0x8f, 0xc1, 0x1a, 0xa6, + 0xa9, 0x6f, 0x78, 0x3a, 0xf3, 0x59, 0xae, 0x74, 0x4b, 0xd9, 0xf9, 0x2c, 0x57, 0xba, 0xa3, 0xbc, + 0xae, 0x0e, 0x60, 0x2b, 0xda, 0x6f, 0xe8, 0x7a, 0x02, 0x19, 0x42, 0xdb, 0x90, 0xef, 0x5a, 0xcf, + 0x2c, 0x27, 0x8a, 0xb3, 0xa7, 0x04, 0x42, 0xfb, 0xc7, 0x5f, 0x75, 0xe2, 0xfb, 0xe5, 0x94, 0x40, + 0x05, 0xb3, 0x27, 0x5d, 0x71, 0xa7, 0x5b, 0x99, 0xff, 0x6f, 0x06, 0x4a, 0xf1, 0x2e, 0x76, 0x07, + 0xd6, 0x87, 0x03, 0x71, 0x64, 0xb5, 0xfd, 0x20, 0x79, 0xd1, 0x63, 0x18, 0xfd, 0xd2, 0xd6, 0x87, + 0x03, 0xf6, 0x36, 0x14, 0xf8, 0x2d, 0x5b, 0xe1, 0xae, 0xda, 0x4e, 0xef, 0x8c, 0x43, 0xc2, 0x69, + 0x82, 0x86, 0x7d, 0x08, 0xe5, 0xb8, 0xb6, 0xc2, 0x7f, 0x73, 0x35, 0xcd, 0x10, 0xa3, 0xb5, 0x84, + 0x52, 0xed, 0xc2, 0x46, 0x3a, 0x43, 0x76, 0x13, 0x80, 0x67, 0x29, 0x39, 0x29, 0x25, 0x08, 0xbb, + 0x0e, 0xd1, 0xe5, 0xdf, 0x16, 0x55, 0xac, 0x16, 0x5f, 0x06, 0x6e, 0xa9, 0xff, 0x2c, 0x0b, 0x90, + 0xe8, 0xc1, 0xd8, 0x11, 0xb1, 0x57, 0x2a, 0x2f, 0x4e, 0xf1, 0x5f, 0x81, 0xb2, 0xe3, 0x19, 0xa6, + 0xfc, 0xa0, 0x47, 0x09, 0x01, 0x34, 0x4c, 0xf2, 0x35, 0xb5, 0x32, 0x0f, 0xa1, 0x61, 0x57, 0xa0, + 0x30, 0xf6, 0xfc, 0xa9, 0x11, 0x8a, 0x4b, 0x15, 0x22, 0x85, 0xdb, 0x19, 0x3f, 0x1c, 0x44, 0x6b, + 0xc0, 0xa5, 0x7b, 0x15, 0x38, 0x06, 0x55, 0x01, 0xec, 0x22, 0x0c, 0xb7, 0x23, 0xcb, 0x1d, 0x39, + 0x5e, 0x60, 0x99, 0xb8, 0xc1, 0x14, 0x48, 0xe5, 0x87, 0x08, 0xb4, 0x7b, 0xc1, 0x5b, 0xeb, 0x4f, + 0x6d, 0xd7, 0x08, 0xc5, 0x31, 0x15, 0xb5, 0x36, 0x82, 0x60, 0x75, 0xbf, 0x0a, 0x3c, 0xe1, 0xa4, + 0xe2, 0x77, 0xb2, 0x4a, 0x08, 0xa0, 0xea, 0xbe, 0x0a, 0x60, 0x05, 0x23, 0x63, 0xc6, 0x33, 0x2f, + 0xf3, 0x8b, 0xb1, 0x02, 0xb2, 0x7b, 0xc1, 0xba, 0xb0, 0x31, 0x3c, 0xc6, 0xed, 0xd7, 0x6b, 0x19, + 0xa1, 0xd1, 0xf4, 0x1c, 0xe1, 0x3e, 0xba, 0xb3, 0x68, 0x30, 0x3c, 0x48, 0x93, 0xf1, 0x58, 0xd2, + 0x05, 0x5e, 0x34, 0xe0, 0xed, 0x91, 0x75, 0x6c, 0xf9, 0x13, 0x6e, 0x7c, 0x54, 0x52, 0xb7, 0x21, + 0x38, 0x86, 0xac, 0x8f, 0x8a, 0x9d, 0x24, 0xae, 0x37, 0xe0, 0xd2, 0x8a, 0xcc, 0xbf, 0x55, 0x30, + 0xe2, 0x3f, 0x5c, 0x87, 0x8a, 0x94, 0x3f, 0x9d, 0x13, 0x18, 0x33, 0xf2, 0x73, 0xc5, 0xd1, 0xd8, + 0x65, 0x01, 0xe1, 0x77, 0x4f, 0x46, 0x46, 0x68, 0x38, 0xde, 0x44, 0xba, 0x55, 0x23, 0x20, 0x1d, + 0x93, 0x8e, 0xee, 0x8c, 0xa9, 0x15, 0xcc, 0x8c, 0x51, 0xb4, 0x42, 0x12, 0x40, 0xf2, 0x92, 0x4c, + 0x4e, 0x7e, 0x49, 0x46, 0xe1, 0x67, 0xbc, 0x3c, 0xac, 0x86, 0x8e, 0x74, 0x6f, 0x41, 0x25, 0xd2, + 0x1b, 0xb1, 0x14, 0x71, 0x0c, 0x19, 0x81, 0x3a, 0x26, 0xbb, 0x0b, 0xc9, 0x4b, 0x39, 0xba, 0x11, + 0xe8, 0xde, 0x38, 0x8a, 0xe9, 0x8b, 0xc1, 0x8d, 0xa0, 0x3f, 0x8e, 0xcf, 0x06, 0xa6, 0x9e, 0x19, + 0x8f, 0x2f, 0x02, 0x48, 0xc6, 0x3d, 0x80, 0x4b, 0xc2, 0x61, 0x69, 0x99, 0xfa, 0xd8, 0xb6, 0x1c, + 0x53, 0xb7, 0x4d, 0x7e, 0xe6, 0x9e, 0xd7, 0xb6, 0x62, 0xd4, 0x1e, 0x62, 0x3a, 0x26, 0x69, 0x58, + 0xc2, 0x4a, 0x35, 0xed, 0x89, 0x15, 0x84, 0x22, 0x02, 0xad, 0xca, 0x81, 0x2d, 0x82, 0xa9, 0x4e, + 0xb4, 0x44, 0x1a, 0x61, 0x48, 0xf7, 0xde, 0x62, 0x75, 0x2f, 0x13, 0x5d, 0x89, 0xe1, 0x9a, 0xde, + 0x2b, 0x14, 0xbd, 0x25, 0x42, 0x90, 0xc5, 0x4a, 0x89, 0x43, 0x8b, 0xef, 0xc2, 0x26, 0x22, 0x45, + 0xa5, 0x88, 0x84, 0xdf, 0x85, 0xaa, 0x8d, 0x3c, 0x87, 0x57, 0x08, 0x81, 0xea, 0xff, 0x9f, 0x07, + 0x48, 0x0c, 0x7d, 0xba, 0x66, 0x47, 0x1e, 0xb8, 0x78, 0xe4, 0x8a, 0x94, 0xee, 0x98, 0xec, 0x21, + 0x5c, 0x11, 0x97, 0xfa, 0xe2, 0xb0, 0x10, 0xdb, 0xd5, 0x8f, 0x8d, 0x28, 0x0c, 0x8d, 0x09, 0x2c, + 0x3f, 0xf0, 0xef, 0xb8, 0xbb, 0x06, 0x9a, 0x0d, 0x9b, 0x32, 0x4f, 0x78, 0x31, 0x4b, 0x7b, 0xfc, + 0x65, 0x65, 0x34, 0x61, 0x1f, 0x5e, 0xcc, 0xd8, 0x7b, 0x70, 0xd9, 0xb7, 0xc6, 0xbe, 0x15, 0x9c, + 0xe8, 0x61, 0x20, 0x17, 0xc6, 0xe3, 0x44, 0xb7, 0x04, 0x72, 0x18, 0xc4, 0x65, 0xbd, 0x07, 0x97, + 0x45, 0xe7, 0x2e, 0x54, 0x8f, 0x7b, 0xd2, 0xb7, 0x38, 0x52, 0xae, 0x1d, 0x05, 0x0f, 0x93, 0xf7, + 0x23, 0x7a, 0x9b, 0xa8, 0xa4, 0x95, 0xb9, 0xa7, 0x43, 0xdc, 0xc2, 0x27, 0x17, 0x06, 0x2d, 0xdc, + 0x92, 0xc6, 0x13, 0x4c, 0x85, 0x1c, 0x8e, 0x3d, 0x0d, 0xdd, 0xc6, 0xc3, 0x8d, 0x07, 0xf4, 0xf6, + 0x12, 0xbd, 0xa0, 0xe0, 0x99, 0x96, 0x46, 0x38, 0xf6, 0x0e, 0xce, 0x8b, 0xa4, 0xd9, 0xd1, 0xf3, + 0x21, 0x3c, 0x32, 0x42, 0x49, 0x1a, 0xaa, 0xf1, 0x87, 0x44, 0xde, 0x02, 0x26, 0xd5, 0x3c, 0xa2, + 0xae, 0x12, 0xf5, 0x66, 0x5c, 0x6d, 0x41, 0xfc, 0x06, 0x50, 0x15, 0x93, 0x20, 0x88, 0x05, 0x7b, + 0x1f, 0x91, 0x22, 0x70, 0xe2, 0x72, 0xd2, 0x3a, 0xdd, 0x08, 0xf5, 0xf0, 0xc4, 0xd2, 0x2d, 0xd7, + 0xa4, 0x9b, 0x98, 0x25, 0x6d, 0x2b, 0x6e, 0x68, 0x23, 0x1c, 0x9e, 0x58, 0x68, 0xb1, 0x4b, 0x1e, + 0xd5, 0xcd, 0x17, 0x7b, 0x54, 0x3f, 0x82, 0x7a, 0x5a, 0xa7, 0x96, 0xba, 0x9b, 0xdf, 0x64, 0xde, + 0x96, 0xa3, 0x4b, 0xe2, 0x1e, 0xbf, 0x0f, 0x5b, 0x27, 0x46, 0x90, 0xd6, 0xc7, 0xc9, 0xd1, 0x5b, + 0xd2, 0x36, 0x4f, 0x8c, 0x40, 0x56, 0xc2, 0xd5, 0x3f, 0xc8, 0xc0, 0x46, 0xda, 0xf5, 0xc1, 0xaf, + 0x87, 0xf1, 0x8b, 0x53, 0x19, 0x5a, 0x63, 0x51, 0x12, 0xd7, 0x02, 0x0f, 0x41, 0x99, 0x4f, 0xdd, + 0x68, 0x2d, 0xcc, 0x4e, 0xf9, 0x75, 0x2a, 0xf6, 0x26, 0x14, 0x67, 0xa7, 0x5c, 0x42, 0x3f, 0x6f, + 0xf6, 0x15, 0x66, 0x3c, 0x74, 0xfe, 0x4d, 0x28, 0xce, 0x4f, 0x93, 0x37, 0x0a, 0x56, 0x92, 0xce, + 0x89, 0x54, 0xfd, 0x47, 0xeb, 0x50, 0x95, 0x3d, 0x76, 0xdf, 0x24, 0xd2, 0xe4, 0x5b, 0x85, 0x0d, + 0xed, 0x50, 0x74, 0xab, 0x4e, 0xb1, 0xe9, 0xd8, 0x4f, 0x3c, 0xcc, 0x04, 0x4e, 0x8c, 0xa0, 0x31, + 0x0f, 0x3d, 0x14, 0xf9, 0x28, 0x4a, 0x3d, 0x27, 0x8a, 0xdf, 0xe7, 0x2b, 0x03, 0x65, 0x82, 0x08, + 0xdd, 0x7f, 0x4f, 0x5c, 0x0f, 0xa2, 0xbb, 0x83, 0x14, 0x08, 0x9a, 0x5f, 0x9a, 0x2f, 0xd5, 0xe8, + 0xea, 0x20, 0x05, 0xbe, 0x3e, 0x84, 0xcd, 0xe4, 0x32, 0x06, 0x67, 0x29, 0x2c, 0xb1, 0xd4, 0xe2, + 0x9b, 0x18, 0xe2, 0x5d, 0x83, 0x9a, 0x1d, 0xe8, 0x9e, 0x63, 0x46, 0xb7, 0xbe, 0x8a, 0xd1, 0x79, + 0x4a, 0xdf, 0x31, 0xc5, 0x85, 0x55, 0x4e, 0xe3, 0x5a, 0x67, 0x11, 0x4d, 0x7c, 0xe6, 0xd2, 0xb3, + 0xce, 0xc4, 0xed, 0xaf, 0x3f, 0xcb, 0xc0, 0xd6, 0x92, 0x93, 0x0e, 0x45, 0x7b, 0xf2, 0xe6, 0x17, + 0xfe, 0x44, 0x23, 0x73, 0x6a, 0x84, 0xa3, 0x13, 0x7d, 0xe6, 0x5b, 0x63, 0xfb, 0x3c, 0x7a, 0xb8, + 0x8c, 0x60, 0x87, 0x04, 0x42, 0xe9, 0xcf, 0xcf, 0x0d, 0xf9, 0x31, 0x06, 0x17, 0x7c, 0xfc, 0xac, + 0xb0, 0x4b, 0xe7, 0x17, 0x51, 0x60, 0x6c, 0xee, 0x39, 0x81, 0xb1, 0xd7, 0xa1, 0xec, 0x7a, 0x14, + 0x04, 0x35, 0x3b, 0x15, 0xd1, 0x63, 0x45, 0xd7, 0x0b, 0xfb, 0xee, 0xe1, 0x29, 0x7b, 0x08, 0x97, + 0xe7, 0x01, 0xc5, 0x0d, 0x1c, 0x5b, 0x7e, 0x70, 0x62, 0xc7, 0xe6, 0x32, 0x17, 0x20, 0x97, 0xe6, + 0x81, 0x75, 0x10, 0xe3, 0x78, 0x4b, 0xd4, 0x1b, 0x50, 0xe8, 0xc4, 0xce, 0xc5, 0x38, 0x12, 0x2f, + 0x2b, 0x5e, 0xfb, 0xf1, 0xa0, 0xcc, 0xad, 0xce, 0x03, 0x63, 0xc6, 0xee, 0x43, 0x76, 0x6a, 0xcc, + 0xc4, 0x61, 0x65, 0x3d, 0x3e, 0xd1, 0xe5, 0xd8, 0x07, 0x07, 0xc6, 0x8c, 0xef, 0xfe, 0x48, 0x74, + 0xfd, 0x23, 0x28, 0x45, 0x80, 0x6f, 0xb5, 0x63, 0xff, 0xcb, 0x75, 0x28, 0xb7, 0xe4, 0xc3, 0x81, + 0x91, 0xe1, 0xea, 0xa1, 0x3f, 0x77, 0x51, 0x81, 0x8e, 0xde, 0x35, 0x19, 0x19, 0xee, 0x50, 0x80, + 0xa2, 0xa9, 0xbd, 0xfe, 0x82, 0xa9, 0x7d, 0x03, 0xc0, 0x27, 0xdf, 0x1a, 0xb9, 0xd7, 0xb2, 0x71, + 0x64, 0x72, 0xc7, 0xec, 0x98, 0xe7, 0xab, 0x43, 0xac, 0x72, 0xdf, 0x3c, 0xc4, 0x2a, 0xbf, 0x32, + 0xc4, 0xea, 0x6e, 0xb2, 0xbd, 0xe0, 0x14, 0xc7, 0x82, 0xcb, 0x7c, 0x93, 0x9b, 0xc5, 0x97, 0xa1, + 0xb0, 0xf4, 0xef, 0xc1, 0x46, 0xd4, 0x3a, 0x91, 0x1f, 0xa4, 0xee, 0x5f, 0x09, 0x1c, 0x3f, 0x4d, + 0xa8, 0x85, 0x72, 0x32, 0xbd, 0x64, 0x2b, 0x2f, 0x09, 0xe3, 0xfa, 0x4b, 0x19, 0x60, 0xc2, 0x17, + 0xb4, 0x37, 0x77, 0x9c, 0xa1, 0x75, 0x4e, 0x92, 0xe1, 0x3e, 0x6c, 0x89, 0xc3, 0x0e, 0xe9, 0x8a, + 0xac, 0x38, 0xde, 0xe7, 0x88, 0xe4, 0x78, 0x7f, 0xd5, 0x6d, 0xda, 0xf5, 0x95, 0xb7, 0x69, 0x57, + 0xdf, 0xd2, 0xbd, 0x05, 0x15, 0xf9, 0x2e, 0x2a, 0xd7, 0x93, 0xc0, 0x88, 0xaf, 0xa1, 0xaa, 0x7f, + 0xb6, 0x0e, 0x90, 0xf8, 0xab, 0x7e, 0xd5, 0xf1, 0x71, 0x2b, 0x86, 0x24, 0xbb, 0x6a, 0x48, 0xee, + 0x81, 0x22, 0xd3, 0x49, 0x97, 0xa2, 0x37, 0x12, 0xc2, 0x48, 0xcd, 0xb1, 0x03, 0xf9, 0x36, 0x2a, + 0x5d, 0x58, 0x11, 0x21, 0x45, 0x1c, 0xc9, 0x9d, 0xe7, 0x62, 0x01, 0x96, 0xec, 0x80, 0xcb, 0x64, + 0xf6, 0x29, 0x5c, 0x8b, 0x39, 0xf5, 0x33, 0x3b, 0x3c, 0xf1, 0xe6, 0xa1, 0x58, 0xac, 0x81, 0x90, + 0x52, 0x57, 0xa2, 0x9c, 0x9e, 0x72, 0x34, 0x5f, 0xaf, 0x01, 0x5a, 0x4b, 0xe3, 0xb9, 0xe3, 0xe8, + 0xa1, 0x75, 0x1e, 0x8a, 0x87, 0x4c, 0xea, 0x29, 0x57, 0x9f, 0x34, 0xbc, 0x5a, 0x69, 0x2c, 0x12, + 0xea, 0xff, 0xc8, 0x42, 0xfe, 0x47, 0x73, 0xcb, 0xbf, 0x60, 0x1f, 0x41, 0x39, 0x08, 0xa7, 0xa1, + 0x7c, 0xea, 0x2e, 0x9e, 0xbb, 0x21, 0x3c, 0x1d, 0x9a, 0x5b, 0x53, 0xcb, 0x0d, 0xb9, 0x0f, 0x1c, + 0x69, 0x69, 0x03, 0xda, 0x86, 0x7c, 0x10, 0x5a, 0xb3, 0x40, 0x44, 0xdb, 0xf2, 0x04, 0xdb, 0x81, + 0xbc, 0xeb, 0x99, 0x56, 0x90, 0x8e, 0xa9, 0xed, 0xa1, 0xc6, 0xc1, 0x11, 0x4c, 0x85, 0x42, 0x3c, + 0xe2, 0x4b, 0x27, 0xdf, 0x1c, 0x43, 0x37, 0x75, 0x2c, 0xc3, 0xb4, 0xdd, 0x49, 0x74, 0xc9, 0x3c, + 0x4e, 0xe3, 0xd6, 0x4a, 0x56, 0x96, 0x31, 0x89, 0x1e, 0xa7, 0x10, 0x49, 0xb6, 0x03, 0x15, 0xfc, + 0xf9, 0xd4, 0xb7, 0x43, 0x6b, 0xf0, 0x28, 0x92, 0xee, 0x12, 0x08, 0x6d, 0x24, 0xd3, 0x0a, 0xad, + 0x51, 0x38, 0xf8, 0x5a, 0x44, 0xaa, 0x96, 0x35, 0x09, 0xc2, 0xbe, 0x07, 0xec, 0xd8, 0x18, 0x9d, + 0x4e, 0x7c, 0x8a, 0x16, 0xf9, 0x7a, 0x6e, 0xf9, 0xb6, 0x15, 0x45, 0xa6, 0x56, 0xa4, 0x4e, 0xd1, + 0xb6, 0x12, 0xb2, 0x1f, 0x71, 0x2a, 0xb4, 0xee, 0xa6, 0xc6, 0x79, 0xcb, 0x9b, 0x89, 0x30, 0x3e, + 0x91, 0x62, 0x8f, 0xe0, 0x0a, 0x6e, 0x8e, 0xd1, 0x55, 0x66, 0x9c, 0x43, 0xc2, 0xbb, 0xca, 0xdf, + 0xb5, 0xba, 0x74, 0x62, 0x04, 0xc9, 0xed, 0x45, 0xee, 0x14, 0x50, 0x7f, 0x0b, 0x6a, 0xa9, 0x7e, + 0x5f, 0x72, 0xec, 0x0d, 0xda, 0xdd, 0x76, 0x73, 0xc8, 0x1d, 0x05, 0xc2, 0xe1, 0xb4, 0x2e, 0x79, + 0xa6, 0x72, 0x92, 0x03, 0x21, 0x4f, 0x7e, 0xad, 0xb6, 0xf6, 0xb8, 0xad, 0x14, 0xd4, 0x5c, 0x29, + 0xab, 0x64, 0xd5, 0xbf, 0xbc, 0x0e, 0x5b, 0x43, 0xdf, 0x70, 0x03, 0x83, 0xeb, 0x30, 0x6e, 0xe8, + 0x7b, 0x0e, 0xfb, 0x1e, 0x94, 0xc2, 0x91, 0x23, 0x4f, 0x84, 0x5b, 0x91, 0xd8, 0x59, 0x20, 0x7d, + 0x30, 0x1c, 0xf1, 0x23, 0x91, 0x62, 0xc8, 0x7f, 0xb0, 0x77, 0x20, 0x7f, 0x6c, 0x4d, 0x6c, 0x57, + 0x48, 0xde, 0xcb, 0x8b, 0x8c, 0xbb, 0x88, 0xdc, 0x5f, 0xd3, 0x38, 0x15, 0x7b, 0x0f, 0x0a, 0x23, + 0x6f, 0x1a, 0x6d, 0x79, 0xc9, 0x3d, 0x46, 0xa9, 0x20, 0xc4, 0xee, 0xaf, 0x69, 0x82, 0x8e, 0x7d, + 0x04, 0x25, 0xdf, 0x73, 0x1c, 0xec, 0x77, 0xb1, 0x19, 0xd6, 0x17, 0x79, 0x34, 0x81, 0xdf, 0x5f, + 0xd3, 0x62, 0x5a, 0xf5, 0x01, 0x14, 0x45, 0x65, 0xb1, 0x1b, 0x76, 0xdb, 0x8f, 0x3b, 0xa2, 0x07, + 0x9b, 0xfd, 0x83, 0x83, 0xce, 0x90, 0x5f, 0xd2, 0xd6, 0xfa, 0xdd, 0xee, 0x6e, 0xa3, 0xf9, 0xb9, + 0xb2, 0xbe, 0x5b, 0x82, 0x02, 0x1f, 0x1e, 0xf5, 0x77, 0x32, 0xb0, 0xb9, 0xd0, 0x00, 0xf6, 0x09, + 0xe4, 0xc8, 0xbe, 0xe2, 0xdd, 0x73, 0x67, 0x65, 0x2b, 0xa5, 0x34, 0xd7, 0xb4, 0x91, 0x43, 0xfd, + 0x14, 0x36, 0xd2, 0x70, 0xc9, 0x99, 0x54, 0x83, 0xb2, 0xd6, 0x6e, 0xb4, 0xf4, 0x7e, 0xaf, 0xfb, + 0x25, 0xf7, 0xcd, 0x52, 0xf2, 0xa9, 0xd6, 0x21, 0xff, 0xcf, 0x6f, 0x82, 0xb2, 0xd8, 0x31, 0xec, + 0x31, 0x5a, 0x4d, 0xd3, 0x99, 0x63, 0x91, 0x72, 0x2a, 0x0d, 0xd9, 0xcd, 0x15, 0x3d, 0x29, 0xc8, + 0x78, 0x98, 0xd3, 0x28, 0x95, 0x56, 0x7f, 0x0b, 0xd8, 0x72, 0x0f, 0xfe, 0xea, 0xb2, 0xff, 0xaf, + 0x19, 0xc8, 0x1d, 0x3a, 0x86, 0xcb, 0x6e, 0x43, 0x9e, 0x9e, 0x4c, 0x12, 0xf2, 0x5b, 0x5e, 0x4d, + 0x38, 0x2d, 0x08, 0xc7, 0xde, 0x82, 0x6c, 0x38, 0x8a, 0xee, 0x86, 0x5f, 0x7d, 0xce, 0xe4, 0xdb, + 0x5f, 0xd3, 0x90, 0x8a, 0xdd, 0x83, 0xac, 0x69, 0x46, 0xf7, 0x24, 0x84, 0x4b, 0xa9, 0x65, 0x84, + 0x46, 0xcb, 0x1a, 0xdb, 0xae, 0x2d, 0x9e, 0x78, 0x42, 0x12, 0xf6, 0x3a, 0x64, 0xcd, 0x91, 0x93, + 0xbe, 0xf4, 0xc2, 0x9d, 0x08, 0x71, 0x86, 0xe6, 0xc8, 0x41, 0xdd, 0x2f, 0xf4, 0x2f, 0x74, 0x7f, + 0xee, 0x52, 0xbc, 0x6c, 0x20, 0xec, 0xb1, 0x0a, 0x6a, 0x31, 0x73, 0x0a, 0xba, 0x0d, 0xc4, 0x25, + 0xd3, 0x99, 0x6f, 0xcd, 0x0c, 0x3f, 0xb6, 0xc4, 0xec, 0xe0, 0x90, 0x03, 0x76, 0x0b, 0x40, 0xef, + 0xe2, 0xaa, 0x6f, 0xd3, 0x7b, 0x3e, 0xa8, 0xd2, 0xab, 0xd1, 0xaf, 0x15, 0x4f, 0x28, 0x0a, 0x8c, + 0xfa, 0xe7, 0x59, 0xa8, 0x48, 0xf5, 0x61, 0x1f, 0x40, 0xc9, 0x4c, 0x2f, 0xc4, 0x6b, 0x4b, 0x95, + 0x7e, 0xd0, 0x8a, 0x96, 0xa0, 0x29, 0xa6, 0x37, 0x9d, 0xb7, 0x85, 0xfa, 0x33, 0xc3, 0xb7, 0xf9, + 0x3b, 0x71, 0xeb, 0xf2, 0xc1, 0xd7, 0xc0, 0x0a, 0x9f, 0x44, 0x98, 0xfd, 0x35, 0xad, 0x1a, 0x48, + 0x69, 0xb2, 0x3b, 0x44, 0x93, 0xb2, 0xa9, 0xb7, 0xf9, 0x38, 0x70, 0x7f, 0x4d, 0x8b, 0xf0, 0x48, + 0x6a, 0x9d, 0x5b, 0xa3, 0x79, 0x18, 0xd9, 0x1d, 0xb5, 0xa8, 0x41, 0x04, 0xa4, 0x07, 0x42, 0xf9, + 0x4f, 0xf6, 0x10, 0xa5, 0xad, 0xe1, 0x38, 0x1e, 0x29, 0x6b, 0x79, 0xf9, 0x18, 0xaa, 0x15, 0xc3, + 0xf9, 0x83, 0xa4, 0x51, 0x8a, 0xdd, 0x85, 0xbc, 0x17, 0x9e, 0x58, 0x91, 0x36, 0x1f, 0xbd, 0x0c, + 0x84, 0xa0, 0x56, 0xb3, 0x8b, 0x33, 0x85, 0xd0, 0xea, 0x1f, 0x66, 0xa0, 0x28, 0x7a, 0x80, 0x6d, + 0x41, 0x6d, 0xd0, 0x1e, 0xea, 0x4f, 0x1a, 0x5a, 0xa7, 0xb1, 0xdb, 0x6d, 0x8b, 0xbb, 0x3a, 0x8f, + 0xb5, 0x46, 0x4f, 0x08, 0x48, 0xad, 0xfd, 0xa4, 0xff, 0x79, 0x9b, 0x7b, 0x68, 0x5b, 0xed, 0xde, + 0x97, 0x4a, 0x96, 0x1f, 0x47, 0xb4, 0x0f, 0x1b, 0x1a, 0xca, 0xca, 0x0a, 0x14, 0xdb, 0x5f, 0xb4, + 0x9b, 0x47, 0x24, 0x2c, 0x37, 0x00, 0x5a, 0xed, 0x46, 0xb7, 0xdb, 0x6f, 0xa2, 0xf0, 0x2c, 0x30, + 0x06, 0x1b, 0x4d, 0xad, 0xdd, 0x18, 0xb6, 0xf5, 0x46, 0xb3, 0xd9, 0x3f, 0xea, 0x0d, 0x95, 0x22, + 0x96, 0xd8, 0xe8, 0x0e, 0xdb, 0x5a, 0x0c, 0xa2, 0xd7, 0xda, 0x5a, 0x5a, 0xff, 0x30, 0x86, 0x94, + 0x77, 0xcb, 0x68, 0x03, 0xd2, 0x58, 0xa9, 0x7f, 0xbc, 0x05, 0x1b, 0xe9, 0xa9, 0xc9, 0x3e, 0x86, + 0x92, 0x69, 0xa6, 0xc6, 0xf8, 0xc6, 0xaa, 0x29, 0xfc, 0xa0, 0x65, 0x46, 0xc3, 0xcc, 0x7f, 0xb0, + 0xd7, 0xa2, 0x85, 0xb4, 0xbe, 0xb4, 0x90, 0xa2, 0x65, 0xf4, 0x03, 0xd8, 0x14, 0xcf, 0xd5, 0x98, + 0x46, 0x68, 0x1c, 0x1b, 0x81, 0x95, 0x5e, 0x25, 0x4d, 0x42, 0xb6, 0x04, 0x6e, 0x7f, 0x4d, 0xdb, + 0x18, 0xa5, 0x20, 0xec, 0xfb, 0xb0, 0x61, 0x70, 0xef, 0x4f, 0xc4, 0x9f, 0x93, 0xd5, 0xd0, 0x06, + 0x39, 0x81, 0x12, 0xf6, 0x9a, 0x21, 0x03, 0x70, 0x22, 0x9a, 0xbe, 0x37, 0x4b, 0x98, 0xf3, 0xa9, + 0x13, 0x58, 0xdf, 0x9b, 0x49, 0xbc, 0x55, 0x53, 0x4a, 0xb3, 0x8f, 0xa0, 0x2a, 0x6a, 0x9e, 0xb8, + 0x3a, 0xe2, 0x25, 0xcb, 0xab, 0x4d, 0x6a, 0xe5, 0xfe, 0x9a, 0x56, 0x19, 0x25, 0x49, 0xf6, 0x08, + 0x75, 0xc9, 0x44, 0x09, 0x2f, 0xca, 0x73, 0x8d, 0x6a, 0x1b, 0x71, 0x81, 0x11, 0xa7, 0xd8, 0x7b, + 0x00, 0x54, 0x4f, 0xce, 0x53, 0x4a, 0xc5, 0x26, 0xf9, 0xde, 0x2c, 0x62, 0x29, 0x9b, 0x51, 0x42, + 0xaa, 0x1e, 0x77, 0x54, 0x95, 0x97, 0xab, 0x47, 0xce, 0xaa, 0xa4, 0x7a, 0xdc, 0xc7, 0x15, 0x57, + 0x8f, 0xb3, 0xc1, 0x52, 0xf5, 0x22, 0x2e, 0x5e, 0x3d, 0xce, 0x14, 0x55, 0x8f, 0xf3, 0x54, 0x16, + 0xab, 0x17, 0xb1, 0x50, 0xf5, 0x38, 0xc7, 0xf7, 0x97, 0xac, 0x87, 0xea, 0x73, 0xad, 0x07, 0x1c, + 0xb6, 0xb4, 0xfd, 0xf0, 0x7d, 0xd8, 0x08, 0x4e, 0xbc, 0x33, 0x49, 0x80, 0xd4, 0x64, 0xee, 0xc1, + 0x89, 0x77, 0x26, 0x4b, 0x90, 0x5a, 0x20, 0x03, 0xb0, 0xb6, 0xbc, 0x89, 0x74, 0x5a, 0xba, 0x21, + 0xd7, 0x96, 0x5a, 0xf8, 0xc4, 0xb6, 0xce, 0xb0, 0xb6, 0x46, 0x94, 0xc0, 0x4e, 0x49, 0xdc, 0x3e, + 0x81, 0x70, 0xe4, 0xa4, 0x22, 0x7a, 0x44, 0x49, 0x10, 0x3b, 0x80, 0x02, 0x9c, 0x5b, 0x73, 0x57, + 0x66, 0x53, 0xe4, 0xb9, 0x75, 0xe4, 0xa6, 0x18, 0xab, 0x9c, 0x54, 0xb0, 0x26, 0xab, 0x22, 0xb0, + 0xbe, 0x9e, 0x5b, 0xee, 0xc8, 0x12, 0x91, 0x7b, 0xa9, 0x55, 0x31, 0x10, 0xb8, 0x64, 0x55, 0x44, + 0x90, 0x78, 0x5e, 0xc7, 0xec, 0x6c, 0x71, 0x5e, 0x4b, 0xcc, 0x34, 0xaf, 0x63, 0xd6, 0x78, 0x41, + 0xc5, 0xbc, 0x97, 0x96, 0x16, 0x94, 0xc4, 0xcc, 0x17, 0x54, 0xcc, 0xfd, 0x08, 0xc4, 0x6c, 0xe2, + 0x9d, 0x9b, 0x8a, 0xef, 0xe3, 0xb5, 0x16, 0xbd, 0x0b, 0xa3, 0x38, 0x85, 0x73, 0xd5, 0xb7, 0xd0, + 0x5a, 0x11, 0x53, 0xe1, 0xb2, 0x3c, 0x57, 0x35, 0xc2, 0xc4, 0x4b, 0xc9, 0x4f, 0x92, 0x52, 0x61, + 0x33, 0x3b, 0xf4, 0xeb, 0xe6, 0x72, 0x61, 0x87, 0x76, 0xe8, 0x27, 0x85, 0x61, 0x8a, 0xbd, 0x03, + 0x34, 0x0d, 0x39, 0x8b, 0x25, 0x8b, 0x6e, 0xec, 0x16, 0xc1, 0x50, 0x32, 0xc5, 0x6f, 0x9c, 0x2c, + 0xa2, 0x8c, 0x91, 0x39, 0xaa, 0x8f, 0xe5, 0xc9, 0xc2, 0x8b, 0x68, 0xb6, 0x9a, 0x38, 0x59, 0x38, + 0x51, 0xd3, 0x1c, 0xb1, 0xfb, 0x40, 0xdc, 0x44, 0x3f, 0x49, 0xbd, 0x3c, 0xe7, 0x7b, 0x33, 0x4e, + 0x5d, 0x44, 0x02, 0xa4, 0xc5, 0x16, 0x38, 0x9e, 0x1b, 0x35, 0xfc, 0x24, 0xd5, 0x02, 0x44, 0xc4, + 0xc2, 0x60, 0x14, 0xa7, 0xd4, 0xdf, 0x2b, 0x40, 0x51, 0xc8, 0x5a, 0x76, 0x09, 0x36, 0x85, 0xc8, + 0x6f, 0x35, 0x86, 0x8d, 0xdd, 0xc6, 0x00, 0x95, 0x34, 0x06, 0x1b, 0x5c, 0xe6, 0xc7, 0xb0, 0x0c, + 0xee, 0x03, 0x24, 0xf4, 0x63, 0xd0, 0x3a, 0xee, 0x03, 0x82, 0x97, 0xbf, 0xf0, 0x99, 0x65, 0x9b, + 0x50, 0xe1, 0x8c, 0x1c, 0x40, 0x57, 0x87, 0x89, 0x8b, 0xa7, 0xf3, 0x12, 0x0b, 0x3f, 0xb0, 0x2c, + 0x24, 0x2c, 0x1c, 0x50, 0x8c, 0x59, 0xa2, 0x13, 0x4d, 0x06, 0x1b, 0x43, 0xed, 0xa8, 0xd7, 0x4c, + 0xca, 0x29, 0xd3, 0x75, 0x4f, 0x9e, 0xcd, 0x93, 0x4e, 0xfb, 0xa9, 0x02, 0xc8, 0xc4, 0x73, 0xa1, + 0x74, 0x05, 0xd5, 0x4c, 0xca, 0x84, 0x92, 0x55, 0x76, 0x15, 0x2e, 0x0d, 0xf6, 0xfb, 0x4f, 0x75, + 0xce, 0x14, 0x37, 0xa1, 0xc6, 0xb6, 0x41, 0x91, 0x10, 0x3c, 0xfb, 0x0d, 0x2c, 0x92, 0xa0, 0x11, + 0xe1, 0x40, 0xd9, 0xa4, 0xc3, 0x7f, 0x84, 0x0d, 0xf9, 0xbe, 0xab, 0x60, 0x53, 0x38, 0x6b, 0xbf, + 0x7b, 0x74, 0xd0, 0x1b, 0x28, 0x5b, 0x58, 0x09, 0x82, 0xf0, 0x9a, 0xb3, 0x38, 0x9b, 0x64, 0xb7, + 0xbe, 0x44, 0x1b, 0x38, 0xc2, 0x9e, 0x36, 0xb4, 0x5e, 0xa7, 0xf7, 0x78, 0xa0, 0x6c, 0xc7, 0x39, + 0xb7, 0x35, 0xad, 0xaf, 0x0d, 0x94, 0xcb, 0x31, 0x60, 0x30, 0x6c, 0x0c, 0x8f, 0x06, 0xca, 0x95, + 0xb8, 0x96, 0x87, 0x5a, 0xbf, 0xd9, 0x1e, 0x0c, 0xba, 0x9d, 0xc1, 0x50, 0xb9, 0xca, 0x2e, 0xc3, + 0x56, 0x52, 0xa3, 0x88, 0xb8, 0x2e, 0x55, 0x54, 0x7b, 0xdc, 0x1e, 0x2a, 0xd7, 0xe2, 0x6a, 0x34, + 0xfb, 0xdd, 0x6e, 0x83, 0x4e, 0xb6, 0xaf, 0x23, 0x11, 0x1d, 0xe6, 0x8b, 0xd6, 0xbc, 0x82, 0xf5, + 0x3a, 0xea, 0xc9, 0xa0, 0x1b, 0xd2, 0xd4, 0x18, 0xb4, 0x7f, 0x74, 0xd4, 0xee, 0x35, 0xdb, 0xca, + 0xab, 0xc9, 0xd4, 0x88, 0x61, 0x37, 0xe3, 0xa9, 0x11, 0x83, 0x6e, 0xc5, 0x65, 0x46, 0xa0, 0x81, + 0xb2, 0x83, 0xf9, 0x89, 0x7a, 0xf4, 0x7a, 0xed, 0xe6, 0x10, 0xdb, 0xfa, 0x5a, 0xdc, 0x8b, 0x47, + 0x87, 0x8f, 0xb5, 0x46, 0xab, 0xad, 0xa8, 0x08, 0xd1, 0xda, 0xbd, 0xc6, 0x41, 0x34, 0xda, 0xb7, + 0xa5, 0xd1, 0x3e, 0xec, 0x0c, 0x35, 0xe5, 0x4e, 0x3c, 0xba, 0x94, 0x7c, 0x9d, 0xbd, 0x02, 0x57, + 0xe5, 0x79, 0xa8, 0x3f, 0xed, 0x0c, 0xf7, 0xc5, 0x41, 0xfc, 0x5d, 0x7e, 0x88, 0x4c, 0xc8, 0x66, + 0xab, 0xc9, 0x23, 0x0e, 0x88, 0x17, 0x53, 0xf7, 0x76, 0xab, 0xf4, 0x6c, 0xbc, 0x50, 0x40, 0xd4, + 0xcf, 0x80, 0xc9, 0xaf, 0x22, 0x8b, 0x00, 0x68, 0x06, 0xb9, 0xb1, 0xef, 0x4d, 0xa3, 0x67, 0x3c, + 0xf0, 0x37, 0xda, 0xdf, 0xb3, 0xf9, 0x31, 0x1d, 0x6e, 0x27, 0xd7, 0xf4, 0x65, 0x90, 0xfa, 0x37, + 0x32, 0xb0, 0x91, 0x56, 0x3e, 0xc8, 0xe1, 0x3a, 0xd6, 0x5d, 0x2f, 0xe4, 0x4f, 0xad, 0x05, 0xf1, + 0x63, 0xc5, 0xe3, 0x9e, 0x17, 0xd2, 0x5b, 0x6b, 0xe4, 0x0e, 0x88, 0x75, 0x09, 0x9e, 0x6b, 0x9c, + 0x66, 0x1d, 0xb8, 0x94, 0x7a, 0x84, 0x3a, 0xf5, 0xd0, 0x5d, 0x3d, 0x7e, 0xae, 0x75, 0xa1, 0xfe, + 0x1a, 0x0b, 0x96, 0xdb, 0x24, 0x1e, 0x5b, 0xc8, 0x25, 0x8f, 0x2d, 0xec, 0x43, 0x2d, 0xa5, 0xeb, + 0x90, 0x17, 0x67, 0x9c, 0xae, 0x69, 0xc9, 0x1e, 0xbf, 0xbc, 0x9a, 0xea, 0x5f, 0xcb, 0x40, 0x55, + 0xd6, 0x7c, 0xbe, 0x73, 0x4e, 0x14, 0x97, 0x24, 0x7e, 0xeb, 0xb6, 0x19, 0xbd, 0x9b, 0x16, 0x81, + 0x3a, 0xf4, 0xb9, 0x0c, 0xee, 0xbb, 0xde, 0x3b, 0x1d, 0xc4, 0xcd, 0x91, 0x41, 0xec, 0x26, 0x00, + 0xbd, 0x69, 0xb4, 0xf7, 0x39, 0x12, 0x88, 0xd7, 0xc2, 0x13, 0x88, 0x7a, 0x0b, 0xca, 0x7b, 0xa7, + 0x51, 0x98, 0x96, 0xfc, 0xe0, 0x60, 0x99, 0xbf, 0xed, 0xa0, 0xfe, 0x51, 0x06, 0x36, 0x12, 0x9f, + 0x05, 0x85, 0x15, 0xf0, 0xc7, 0xcb, 0xf9, 0x74, 0x58, 0x37, 0x8f, 0x93, 0xf3, 0xcf, 0x75, 0xf9, + 0xfc, 0xf3, 0xb6, 0xc8, 0x2c, 0x2b, 0x8b, 0xfc, 0xb8, 0x2c, 0xf1, 0x72, 0xc4, 0x23, 0xa8, 0xe2, + 0x7f, 0xcd, 0x1a, 0x5b, 0xbe, 0x6f, 0x99, 0xe9, 0x6b, 0x29, 0x09, 0x71, 0x8a, 0x88, 0x6c, 0x3c, + 0x71, 0xb2, 0xfa, 0x9c, 0x47, 0xa1, 0xe8, 0xb1, 0xb2, 0xff, 0x9c, 0x85, 0x8a, 0xa4, 0x47, 0x7e, + 0xa3, 0xe9, 0x77, 0x03, 0xca, 0xc9, 0x13, 0x45, 0xe2, 0xba, 0x7e, 0x0c, 0x48, 0x8d, 0x55, 0x76, + 0x61, 0xac, 0xea, 0x50, 0xf4, 0xf9, 0x65, 0x59, 0xe1, 0x42, 0x8e, 0x92, 0x69, 0x67, 0x6d, 0xfe, + 0x25, 0xe7, 0x2b, 0xef, 0x43, 0x55, 0xf2, 0xb4, 0x06, 0xf5, 0x82, 0xfc, 0x4e, 0x55, 0x4c, 0x5f, + 0x49, 0xbc, 0xae, 0x01, 0xbb, 0x0c, 0x85, 0xf1, 0xa9, 0x6e, 0x1e, 0xf3, 0x4b, 0xdc, 0x65, 0x2d, + 0x3f, 0x3e, 0x6d, 0x1d, 0xd3, 0xe9, 0xd3, 0x38, 0x56, 0x9d, 0xb8, 0xff, 0xab, 0x34, 0x8e, 0x14, + 0xa4, 0x7b, 0x50, 0x1c, 0x9f, 0xca, 0x97, 0xb1, 0x97, 0xba, 0xbc, 0x30, 0x3e, 0xa5, 0x2b, 0xd8, + 0xef, 0xc2, 0xb6, 0xd8, 0xbf, 0x8d, 0x40, 0xe7, 0x2f, 0xa3, 0xd0, 0xd3, 0x55, 0xfc, 0xf9, 0xc1, + 0x2d, 0x8e, 0x6b, 0x04, 0x03, 0xc2, 0xe0, 0x8c, 0x53, 0xa1, 0x2a, 0x4d, 0x40, 0xfe, 0xc6, 0x57, + 0x59, 0x4b, 0xc1, 0xd8, 0x27, 0x50, 0x1d, 0x9f, 0xf2, 0x01, 0x1d, 0x7a, 0x07, 0x96, 0xb8, 0x29, + 0xb2, 0xbd, 0x38, 0x94, 0x14, 0xf7, 0x91, 0xa2, 0x64, 0x57, 0xa0, 0xa0, 0x19, 0x67, 0x83, 0x1f, + 0x75, 0x49, 0x89, 0x2c, 0x6b, 0x22, 0xf5, 0x59, 0xae, 0xb4, 0xa1, 0x6c, 0xaa, 0x7f, 0x2f, 0x03, + 0x1b, 0x89, 0x0d, 0x80, 0x8b, 0x90, 0xdd, 0x97, 0xbf, 0x3a, 0x50, 0x5f, 0x34, 0x13, 0x90, 0xe4, + 0xc1, 0xf0, 0x62, 0xc6, 0x5f, 0xc3, 0x5d, 0xf5, 0x2e, 0xdb, 0x2a, 0xd7, 0x77, 0x76, 0xe5, 0x1b, + 0xe6, 0x8f, 0x21, 0x3b, 0xbc, 0x98, 0x71, 0x7f, 0x13, 0x6e, 0x89, 0xdc, 0x36, 0xe5, 0x9b, 0x21, + 0x05, 0x19, 0x7d, 0xde, 0xfe, 0x92, 0x3f, 0x2f, 0x72, 0xa8, 0x75, 0x0e, 0x1a, 0xda, 0x97, 0x3a, + 0x02, 0x48, 0x69, 0xd8, 0xeb, 0x6b, 0xed, 0xce, 0xe3, 0x1e, 0x01, 0x72, 0xe4, 0x8d, 0x4a, 0xaa, + 0xd8, 0x30, 0xcd, 0xbd, 0x53, 0xf9, 0x49, 0xab, 0x4c, 0xea, 0x49, 0xab, 0xf8, 0xf5, 0x37, 0xf9, + 0xd5, 0xcc, 0x30, 0xf6, 0xc7, 0xb3, 0x78, 0x15, 0xc6, 0x4b, 0x9a, 0xbd, 0x01, 0xb9, 0xf1, 0xa9, + 0x75, 0x91, 0x36, 0xf4, 0xd2, 0x0b, 0x88, 0x08, 0xd4, 0x5f, 0x64, 0x80, 0xa5, 0x2a, 0xc2, 0x6d, + 0x8f, 0xef, 0x5a, 0x97, 0x8f, 0xa1, 0x2e, 0x22, 0x31, 0x39, 0x95, 0xe4, 0x6b, 0x17, 0x5d, 0x7a, + 0xd9, 0x4b, 0x22, 0x94, 0x93, 0xa7, 0xe3, 0xd8, 0xbb, 0xc0, 0x1f, 0xc4, 0xa4, 0x30, 0xa1, 0xdc, + 0x73, 0xec, 0x44, 0x2d, 0xa1, 0x49, 0x5e, 0xc0, 0x94, 0x5f, 0xf6, 0xe4, 0x6e, 0xfa, 0xcd, 0x64, + 0xd4, 0x68, 0xcd, 0xab, 0xff, 0x5f, 0x06, 0x2e, 0xa5, 0x27, 0xc4, 0x2f, 0xd7, 0xca, 0xf4, 0x33, + 0xa6, 0xd9, 0xc5, 0x67, 0x4c, 0x57, 0xcd, 0xa7, 0xdc, 0xca, 0xf9, 0xf4, 0xbb, 0x19, 0xd8, 0x96, + 0x7a, 0x3f, 0xb1, 0x16, 0xff, 0x82, 0x6a, 0x26, 0xbd, 0x66, 0x9a, 0x4b, 0xbd, 0x66, 0xaa, 0xfe, + 0x3c, 0x03, 0x57, 0x16, 0x6a, 0xa2, 0x59, 0x7f, 0xa1, 0x75, 0x49, 0xbf, 0x7a, 0x4a, 0x47, 0x05, + 0xd1, 0x13, 0x1c, 0x99, 0x7b, 0x59, 0xe9, 0xd5, 0x53, 0x3a, 0x3f, 0xa2, 0x43, 0xcc, 0x57, 0xc5, + 0x4b, 0x4d, 0x7a, 0x70, 0xe1, 0x8e, 0xc4, 0x60, 0x97, 0x09, 0x32, 0xb8, 0x70, 0x47, 0xea, 0x1f, + 0x67, 0xe0, 0xda, 0x42, 0x1b, 0x1a, 0xf3, 0xd0, 0x13, 0x67, 0xc1, 0x7f, 0x41, 0xcd, 0xb8, 0x05, + 0x15, 0x3a, 0x29, 0x17, 0x07, 0xcc, 0xbc, 0x5b, 0xc1, 0x48, 0xca, 0x55, 0x20, 0x6b, 0x1a, 0x17, + 0xe2, 0xd1, 0x02, 0xfc, 0x89, 0x0b, 0xf6, 0xc4, 0x9b, 0xfb, 0x22, 0x3a, 0x88, 0x7e, 0xab, 0x1f, + 0xc0, 0x56, 0x52, 0xf5, 0xa6, 0x78, 0x7c, 0xf6, 0x16, 0x54, 0x5c, 0xeb, 0x4c, 0x8f, 0x9e, 0xa6, + 0x15, 0x31, 0x6f, 0xae, 0x75, 0x26, 0x08, 0xd4, 0x3d, 0x59, 0x16, 0xc6, 0x1f, 0xf8, 0x70, 0xcc, + 0x54, 0xdc, 0x8e, 0xe7, 0x98, 0x11, 0x0a, 0x73, 0x93, 0x5a, 0x59, 0x74, 0xad, 0x33, 0x9a, 0x87, + 0x67, 0x22, 0x9f, 0x86, 0x29, 0x9e, 0x82, 0x5d, 0xf9, 0xb0, 0xdb, 0x35, 0x28, 0xcd, 0xfc, 0x54, + 0x37, 0x15, 0x67, 0x3e, 0x2f, 0xf6, 0x8e, 0x88, 0xa8, 0x7b, 0x5e, 0x9c, 0x03, 0x8f, 0xb1, 0x13, + 0x1f, 0x00, 0xca, 0x25, 0x1f, 0x00, 0xfa, 0x50, 0x88, 0x41, 0xb2, 0xfb, 0x78, 0xc9, 0x0a, 0x64, + 0x6d, 0xf3, 0x9c, 0x0a, 0xae, 0x69, 0xf8, 0x93, 0x34, 0x39, 0xeb, 0x6b, 0x11, 0xd4, 0x87, 0x3f, + 0xd5, 0x5d, 0xa8, 0x68, 0x29, 0x23, 0xb7, 0x2a, 0xf9, 0x8b, 0x82, 0xf4, 0xdb, 0x57, 0x49, 0x07, + 0x69, 0x95, 0xc4, 0x5d, 0x14, 0xa8, 0x81, 0x10, 0x7c, 0x4f, 0x0c, 0x7f, 0x74, 0x62, 0xf8, 0x5d, + 0xcb, 0x9d, 0x84, 0x27, 0xd8, 0xe5, 0xdc, 0x8d, 0x2b, 0x77, 0x21, 0x70, 0x50, 0x34, 0x1d, 0xb0, + 0x17, 0x1d, 0x22, 0x8f, 0x3e, 0x17, 0xe2, 0x5a, 0x67, 0x82, 0xff, 0x55, 0x00, 0xec, 0x7f, 0x81, + 0xe6, 0x47, 0x90, 0x65, 0xcf, 0x31, 0x39, 0x5a, 0xdd, 0x12, 0xed, 0x15, 0x8f, 0x6e, 0xb4, 0xac, + 0xb1, 0xea, 0x88, 0x91, 0xe7, 0x0d, 0x12, 0x9d, 0xf0, 0x9d, 0x86, 0x91, 0xbd, 0x06, 0xd5, 0xc8, + 0x23, 0x41, 0xcf, 0xad, 0xf1, 0xe2, 0x2b, 0x11, 0xac, 0x37, 0x9f, 0xaa, 0x7f, 0x90, 0x85, 0x6a, + 0x83, 0x47, 0xf6, 0xcc, 0x2e, 0xfa, 0xb3, 0x90, 0xfd, 0x16, 0x5c, 0xa6, 0xf7, 0x57, 0xf8, 0xa3, + 0xca, 0x14, 0x50, 0x43, 0xe1, 0xf9, 0xa2, 0x13, 0xef, 0x4b, 0x9d, 0x28, 0x58, 0x1e, 0x0c, 0x4e, + 0xed, 0x19, 0xbf, 0x15, 0xd2, 0x31, 0xcf, 0xe9, 0x0a, 0x06, 0x8f, 0x0d, 0xa0, 0xa7, 0x5a, 0xd2, + 0x08, 0x7a, 0x7b, 0x01, 0xb3, 0x9f, 0x9d, 0x8a, 0x6c, 0x45, 0xd8, 0x04, 0x02, 0x0f, 0x4f, 0x39, + 0xcd, 0x7d, 0xd8, 0xe2, 0x17, 0xc1, 0x96, 0x37, 0xe0, 0x4d, 0x8e, 0x48, 0xe6, 0xf7, 0x00, 0xb6, + 0xc4, 0x73, 0x31, 0xf4, 0x7e, 0xa8, 0x3e, 0xf2, 0x66, 0x17, 0xe2, 0xe8, 0xf1, 0x8d, 0xe7, 0x54, + 0xb5, 0xc3, 0x49, 0x11, 0xc4, 0xeb, 0xb9, 0x19, 0xa4, 0xa1, 0xd7, 0xdb, 0x70, 0xf5, 0x39, 0x6d, + 0x7a, 0x59, 0x78, 0x43, 0x49, 0x0a, 0x6f, 0xb8, 0xbe, 0x0b, 0xdb, 0xab, 0xca, 0xfb, 0x36, 0x79, + 0xa8, 0x3f, 0xab, 0x01, 0x24, 0x33, 0x36, 0xa5, 0x8e, 0x66, 0x16, 0xd4, 0xd1, 0x6f, 0x15, 0xd4, + 0xf3, 0x01, 0x6c, 0x60, 0x57, 0xe9, 0x09, 0x47, 0x76, 0x25, 0x47, 0x15, 0xa9, 0x86, 0xc9, 0x5d, + 0xd7, 0xe5, 0x90, 0x88, 0xdc, 0xca, 0x90, 0x88, 0xf7, 0xa1, 0xc8, 0x0f, 0xda, 0x02, 0x71, 0xbd, + 0xfa, 0xea, 0xe2, 0xea, 0x7b, 0x20, 0x6e, 0x98, 0x44, 0x74, 0xac, 0x0d, 0x1b, 0x28, 0xfa, 0x7d, + 0x3b, 0x3c, 0x99, 0xca, 0x97, 0xad, 0x6f, 0x2e, 0x73, 0x46, 0x64, 0xe2, 0x1b, 0x2d, 0x72, 0x52, + 0xd2, 0x5e, 0xc3, 0xa9, 0xf0, 0xfe, 0x92, 0xf6, 0x5a, 0x94, 0xb5, 0xd7, 0xe1, 0x94, 0xfb, 0x7c, + 0x51, 0x7b, 0x7d, 0x07, 0x2e, 0x89, 0x5b, 0x6f, 0xc8, 0x80, 0xdd, 0x49, 0xf4, 0x3c, 0xc8, 0x52, + 0x11, 0x6f, 0xae, 0x4c, 0xc9, 0xb6, 0x43, 0xf2, 0x2f, 0x60, 0x7b, 0x74, 0x62, 0xb8, 0x13, 0x4b, + 0x0f, 0x8f, 0x1d, 0x9d, 0xbe, 0xe4, 0xa0, 0x4f, 0x8d, 0x99, 0x50, 0xaa, 0xdf, 0x58, 0xaa, 0x6c, + 0x93, 0x88, 0x87, 0xc7, 0x0e, 0x05, 0xa0, 0xc5, 0x81, 0x33, 0x5b, 0xa3, 0x45, 0xf8, 0xc2, 0xf9, + 0x35, 0x2c, 0x9d, 0x5f, 0x2f, 0xaa, 0xd9, 0x95, 0x15, 0x6a, 0x76, 0xa2, 0x2c, 0x57, 0x65, 0x65, + 0x99, 0xbd, 0x0d, 0x45, 0x71, 0x69, 0x57, 0xf8, 0x7d, 0xd9, 0xf2, 0xea, 0xd0, 0x22, 0x12, 0x2c, + 0x29, 0x8a, 0xa6, 0xa0, 0x77, 0x1d, 0x36, 0x78, 0x49, 0x32, 0x8c, 0xed, 0x0a, 0xa7, 0x67, 0x1c, + 0x2c, 0x27, 0x7c, 0xbc, 0xd7, 0xa5, 0x8c, 0x63, 0x9c, 0xb0, 0xcb, 0x17, 0x38, 0xae, 0xff, 0xdd, + 0x02, 0x14, 0x44, 0xa0, 0xfc, 0x7d, 0xc8, 0x99, 0xbe, 0x37, 0x8b, 0xa3, 0xcd, 0x57, 0x68, 0xed, + 0xf4, 0x29, 0x41, 0x54, 0xf0, 0x1f, 0x40, 0xc1, 0x30, 0x4d, 0x7d, 0x7c, 0x9a, 0x3e, 0x8f, 0x5e, + 0x50, 0xa0, 0xf7, 0xd7, 0xb4, 0xbc, 0x41, 0x9a, 0xf4, 0xc7, 0x50, 0x46, 0xfa, 0x24, 0xfc, 0xb4, + 0xb2, 0x6c, 0x16, 0x44, 0xaa, 0xee, 0xfe, 0x9a, 0x56, 0x32, 0x22, 0xb5, 0xf7, 0xd7, 0xd3, 0x9e, + 0xfd, 0xdc, 0x52, 0x03, 0x17, 0xf4, 0xb4, 0x05, 0x1f, 0xff, 0x6f, 0x00, 0x77, 0xf5, 0xc6, 0x3b, + 0x76, 0x5e, 0x3e, 0xfa, 0x5c, 0xda, 0xdf, 0xf7, 0xd7, 0x34, 0xbe, 0x6f, 0x45, 0xfb, 0xfd, 0x87, + 0x91, 0xd7, 0x3d, 0xfe, 0x8c, 0xd2, 0x8a, 0x9e, 0x41, 0x31, 0x18, 0xbb, 0xde, 0x49, 0x26, 0x22, + 0x9b, 0x19, 0xbd, 0xed, 0x2e, 0x4e, 0x4b, 0x64, 0xb6, 0x78, 0x57, 0x27, 0xb6, 0x78, 0x8b, 0xff, + 0x04, 0x2a, 0xdc, 0x09, 0xcb, 0xf9, 0x4a, 0x4b, 0x5d, 0x9b, 0x6c, 0xca, 0x74, 0xac, 0x97, 0x6c, + 0xd1, 0xcd, 0xa8, 0x9d, 0xbe, 0x25, 0x9f, 0x9c, 0xdc, 0x58, 0xd9, 0x51, 0x5a, 0x7c, 0x88, 0xc2, + 0x1b, 0xab, 0x71, 0x1e, 0xd6, 0x85, 0x6d, 0x71, 0xc4, 0xc0, 0x37, 0xe0, 0x68, 0xcf, 0x84, 0xa5, + 0xf1, 0x4a, 0xed, 0xd0, 0xfb, 0x6b, 0x1a, 0x33, 0x96, 0xf7, 0xed, 0x26, 0x6c, 0x45, 0x55, 0xa2, + 0x9d, 0x55, 0x0a, 0x9b, 0x92, 0x9b, 0x94, 0xec, 0xbb, 0xfb, 0x6b, 0xda, 0xa6, 0x91, 0x06, 0xb1, + 0x0e, 0x5c, 0x8a, 0x32, 0x21, 0x57, 0xbb, 0xe8, 0x99, 0xea, 0xd2, 0x28, 0xca, 0x7b, 0xf5, 0xfe, + 0x9a, 0xb6, 0x65, 0x2c, 0x6d, 0xe0, 0x07, 0x51, 0x7d, 0x64, 0xe5, 0x90, 0xaf, 0xc4, 0x5b, 0x2b, + 0xbb, 0x29, 0xd1, 0x54, 0xe3, 0x9a, 0x25, 0xa0, 0x24, 0x8e, 0xe1, 0xba, 0x06, 0x57, 0x56, 0x4b, + 0x18, 0x79, 0x9b, 0xc9, 0xf1, 0x6d, 0x46, 0x95, 0xb7, 0x99, 0xc5, 0x77, 0x5a, 0xa4, 0x4d, 0xe7, + 0x87, 0x50, 0x4b, 0x89, 0x58, 0x56, 0x81, 0x62, 0xf4, 0xae, 0x3d, 0x5d, 0x8e, 0x69, 0xf6, 0x0f, + 0xbf, 0x54, 0x32, 0x08, 0xee, 0xf4, 0x06, 0xc3, 0x46, 0x6f, 0xa8, 0xac, 0xf3, 0xc4, 0x61, 0xb7, + 0xd1, 0x6c, 0x2b, 0x59, 0xf5, 0xe7, 0x59, 0x28, 0xc7, 0xa7, 0x6c, 0xdf, 0xdd, 0x1b, 0x16, 0xbb, + 0x99, 0xb2, 0xb2, 0x9b, 0x69, 0xc1, 0xd4, 0xe3, 0x5f, 0xab, 0xc8, 0x45, 0x5f, 0x30, 0x90, 0x0d, + 0xaa, 0x60, 0xf9, 0xfa, 0x7e, 0xfe, 0x1b, 0x5e, 0xdf, 0x97, 0x23, 0xd0, 0x0b, 0xe9, 0x08, 0xf4, + 0x85, 0x6f, 0x1b, 0x14, 0xe9, 0xd5, 0x71, 0xf9, 0xdb, 0x06, 0xf4, 0x91, 0xd7, 0x27, 0xb6, 0x75, + 0x26, 0x42, 0xb6, 0x45, 0x2a, 0xbd, 0x43, 0xc3, 0x4b, 0x76, 0xe8, 0x6f, 0x22, 0xed, 0x1f, 0xc2, + 0xf6, 0xf8, 0x34, 0x7e, 0xeb, 0x3c, 0x71, 0xae, 0x54, 0xa9, 0x4a, 0x2b, 0x71, 0xec, 0x8d, 0xf8, + 0x4b, 0x73, 0x35, 0xd9, 0x0d, 0x14, 0x8f, 0x56, 0xfc, 0xe9, 0xb9, 0xff, 0x27, 0x03, 0x90, 0x9c, + 0x3f, 0xfd, 0xd2, 0xae, 0x5c, 0xc9, 0x5b, 0x96, 0x7d, 0x81, 0xb7, 0xec, 0x65, 0x2f, 0xd4, 0x7d, + 0x0d, 0xe5, 0xf8, 0xc4, 0xf1, 0xbb, 0x4f, 0xac, 0x6f, 0x55, 0xe4, 0x6f, 0x47, 0x6e, 0xed, 0xf8, + 0xc8, 0xee, 0x97, 0xed, 0x8b, 0x54, 0xf1, 0xd9, 0x97, 0x14, 0x7f, 0xce, 0x7d, 0xcb, 0x71, 0xe1, + 0xbf, 0xe2, 0xd5, 0x24, 0x4f, 0xf4, 0x5c, 0x6a, 0xa2, 0xab, 0x73, 0xe1, 0x20, 0xff, 0xe5, 0x8b, + 0xfe, 0x56, 0x0d, 0xfe, 0x4f, 0x99, 0xc8, 0x8b, 0x1b, 0x3f, 0x4f, 0xff, 0x5c, 0xa5, 0x77, 0xb5, + 0x23, 0xfa, 0xdb, 0x14, 0xf7, 0x42, 0x1f, 0x55, 0xee, 0x45, 0x3e, 0xaa, 0x37, 0x20, 0xcf, 0xb7, + 0xbb, 0xfc, 0xf3, 0xfc, 0x53, 0x1c, 0xff, 0xd2, 0xef, 0xcd, 0xa8, 0xaa, 0x50, 0xf2, 0x79, 0x7b, + 0xb7, 0xa3, 0x7c, 0xa3, 0x6f, 0xe5, 0xd0, 0x0d, 0x99, 0xff, 0x83, 0x4b, 0xd4, 0xef, 0xda, 0x25, + 0x2f, 0x76, 0x5b, 0xa8, 0xff, 0x33, 0x03, 0xb5, 0x54, 0x04, 0xc1, 0x77, 0x28, 0x62, 0xa5, 0x5c, + 0xce, 0xfe, 0x6f, 0x24, 0x97, 0x53, 0x21, 0xbc, 0xa5, 0x74, 0x08, 0x2f, 0x8a, 0xbb, 0x6a, 0xca, + 0x84, 0x59, 0x65, 0xec, 0x64, 0x56, 0x1a, 0x3b, 0x37, 0xe3, 0x4f, 0x86, 0x76, 0x5a, 0x3c, 0x62, + 0xb6, 0xa6, 0x49, 0x10, 0xf6, 0x29, 0x5c, 0x13, 0x4e, 0x04, 0xde, 0x3f, 0xde, 0x58, 0x8f, 0x3f, + 0x28, 0x2a, 0x8c, 0xf2, 0x2b, 0x9c, 0x80, 0x7f, 0x2d, 0x68, 0xdc, 0x88, 0xb0, 0x6a, 0x07, 0x6a, + 0xa9, 0xd0, 0x0c, 0xe9, 0x63, 0xc7, 0x19, 0xf9, 0x63, 0xc7, 0x6c, 0x07, 0xf2, 0x67, 0x27, 0x96, + 0x6f, 0xad, 0x78, 0x52, 0x9a, 0x23, 0xd4, 0xef, 0x43, 0x55, 0x0e, 0x13, 0x63, 0x6f, 0x43, 0xde, + 0x0e, 0xad, 0x69, 0xe4, 0x1e, 0xb9, 0xb2, 0x1c, 0x49, 0xd6, 0x09, 0xad, 0xa9, 0xc6, 0x89, 0xd4, + 0x3f, 0xcc, 0x80, 0xb2, 0x88, 0x93, 0xbe, 0xc8, 0x9c, 0x79, 0xce, 0x17, 0x99, 0xd7, 0x53, 0x95, + 0x5c, 0xf5, 0x51, 0xe5, 0x9d, 0x48, 0x29, 0x59, 0xf1, 0x41, 0x5f, 0x42, 0xb0, 0xbb, 0x50, 0xf2, + 0x2d, 0xfa, 0xdc, 0xad, 0xb9, 0xe2, 0xe6, 0x48, 0x8c, 0x53, 0x7f, 0x3f, 0x03, 0x45, 0x11, 0xd3, + 0xb6, 0xd2, 0x5f, 0xf5, 0x26, 0x14, 0xf9, 0xa7, 0x6f, 0xa3, 0x87, 0xea, 0x96, 0xc2, 0xcc, 0x23, + 0x3c, 0xbb, 0xc9, 0x23, 0xfd, 0xd2, 0xfe, 0xab, 0x43, 0xc7, 0x70, 0x35, 0x82, 0x8b, 0x8f, 0x80, + 0x19, 0x53, 0xf1, 0xb2, 0x01, 0x7f, 0x61, 0x0c, 0x08, 0x44, 0x8f, 0x18, 0xa8, 0xbf, 0x0e, 0x45, + 0x11, 0x33, 0xb7, 0xb2, 0x2a, 0x2f, 0xf9, 0x94, 0xa9, 0xba, 0x03, 0x90, 0x04, 0xd1, 0xad, 0xca, + 0x41, 0xbd, 0x0f, 0xa5, 0x28, 0x6e, 0x0e, 0xe7, 0x5f, 0x52, 0xb4, 0xb8, 0x92, 0x24, 0x57, 0xc6, + 0x11, 0x9f, 0x69, 0xe8, 0x7a, 0xa3, 0x53, 0x72, 0x96, 0xbf, 0x0b, 0x74, 0x3f, 0x6b, 0xb8, 0xf4, + 0x14, 0x5b, 0xfa, 0x7b, 0x1e, 0x31, 0x11, 0xbb, 0x0f, 0xb1, 0xbc, 0x7c, 0x99, 0x6b, 0x41, 0x6d, + 0x44, 0x37, 0xf9, 0x68, 0x96, 0x3d, 0x12, 0xde, 0xd4, 0x2e, 0x3d, 0x05, 0x9a, 0x91, 0x9f, 0x53, + 0x4e, 0xd5, 0x49, 0x93, 0xc8, 0xd4, 0x0d, 0xa8, 0xca, 0xc1, 0x3e, 0x6a, 0x03, 0xb6, 0x0e, 0xac, + 0xd0, 0x40, 0xf9, 0x13, 0x3d, 0x50, 0xc5, 0xe7, 0x2f, 0xfe, 0x48, 0xcf, 0xdf, 0x45, 0x3a, 0x8d, + 0x13, 0xa9, 0x3f, 0xcf, 0x81, 0xb2, 0x88, 0x7b, 0xd1, 0xad, 0xc6, 0x5b, 0x50, 0xf1, 0x68, 0x5e, + 0xa4, 0x3e, 0xf3, 0xc6, 0x41, 0xd2, 0x7d, 0x80, 0xd4, 0x07, 0x7c, 0x4a, 0x76, 0xb0, 0xcf, 0x3f, + 0xe1, 0x73, 0x95, 0x5f, 0x61, 0x73, 0xbc, 0x11, 0x4d, 0xeb, 0x2a, 0xdd, 0x58, 0xeb, 0x7a, 0x23, + 0xba, 0x2c, 0x29, 0xbc, 0x13, 0x3c, 0x02, 0xb5, 0xaa, 0x95, 0x84, 0x4b, 0x82, 0xce, 0xef, 0xc4, + 0x2d, 0x81, 0x30, 0x10, 0x77, 0x80, 0x4b, 0x1c, 0x30, 0x0c, 0xa2, 0x8f, 0x01, 0x8c, 0xc4, 0x37, + 0xc9, 0xb2, 0xf4, 0x31, 0x80, 0xa6, 0x4b, 0x77, 0x25, 0xe9, 0x89, 0xe4, 0x91, 0xf8, 0x1a, 0xa3, + 0xf8, 0x1c, 0x03, 0xa2, 0x6e, 0xf3, 0xaf, 0xb6, 0xf9, 0x56, 0x10, 0xf0, 0x17, 0x21, 0xcb, 0xe2, + 0x29, 0x50, 0x01, 0x8c, 0x1f, 0xdc, 0x15, 0x1f, 0xd4, 0x43, 0x12, 0x10, 0xef, 0x52, 0xf2, 0xcf, + 0xe9, 0x21, 0xc1, 0x35, 0x28, 0xfd, 0xc4, 0x73, 0x2d, 0xf2, 0x72, 0x54, 0xa8, 0x56, 0x45, 0x4c, + 0x1f, 0x18, 0x33, 0xdc, 0x08, 0x1c, 0xba, 0x6e, 0xce, 0x6f, 0x09, 0xf2, 0x84, 0xfa, 0x2f, 0x32, + 0xb0, 0xbd, 0xd8, 0xd7, 0x34, 0x8d, 0xaa, 0x50, 0x6a, 0xf6, 0xbb, 0x7a, 0xaf, 0x71, 0xd0, 0x56, + 0xd6, 0xd8, 0x26, 0x54, 0xfa, 0xbb, 0x9f, 0xb5, 0x9b, 0x43, 0x0e, 0xc8, 0xd0, 0x1b, 0x0e, 0x03, + 0x7d, 0xbf, 0xd3, 0x6a, 0xb5, 0x7b, 0xdc, 0xa2, 0xe8, 0xef, 0x7e, 0xa6, 0x77, 0xfb, 0x4d, 0xfe, + 0x35, 0xad, 0x28, 0xe2, 0x61, 0xa0, 0xe4, 0x28, 0x1e, 0x82, 0x82, 0xe1, 0x31, 0x99, 0xe7, 0x51, + 0xde, 0x4f, 0x07, 0x7a, 0xb3, 0x37, 0x54, 0x0a, 0x98, 0xea, 0x1d, 0x75, 0xbb, 0x94, 0xa2, 0x70, + 0xce, 0x66, 0xff, 0xe0, 0x50, 0x6b, 0x0f, 0x06, 0xfa, 0xa0, 0xf3, 0xe3, 0xb6, 0x52, 0xa2, 0x92, + 0xb5, 0xce, 0xe3, 0x4e, 0x8f, 0x03, 0xca, 0xac, 0x08, 0xd9, 0x83, 0x4e, 0x8f, 0xbf, 0x5d, 0x71, + 0xd0, 0xf8, 0x42, 0xa9, 0xe0, 0x8f, 0xc1, 0xd1, 0x81, 0x52, 0x65, 0x65, 0xc8, 0x77, 0xdb, 0x4f, + 0xda, 0x5d, 0xa5, 0xa6, 0xfe, 0x9b, 0x6c, 0xa4, 0x11, 0x53, 0x9c, 0xd3, 0x37, 0xd1, 0x02, 0x57, + 0x9d, 0x2f, 0xc6, 0x9d, 0x96, 0x95, 0x3a, 0xed, 0x9b, 0x7c, 0x3f, 0xfa, 0x36, 0xd4, 0xe2, 0xe0, + 0x00, 0xe9, 0x01, 0xff, 0x6a, 0x04, 0x5c, 0x71, 0x7c, 0x51, 0x58, 0x71, 0x7c, 0x31, 0xb3, 0x43, + 0xb4, 0xb2, 0x51, 0xe6, 0xf2, 0x99, 0x54, 0x46, 0x08, 0xff, 0xca, 0xfb, 0x2b, 0x40, 0x09, 0x7d, + 0xee, 0xda, 0xd1, 0xb7, 0x3d, 0x4b, 0x08, 0x38, 0x72, 0xed, 0x70, 0x31, 0x38, 0xa1, 0xbc, 0x14, + 0x9c, 0x20, 0x6f, 0xce, 0x90, 0xde, 0x9c, 0xd3, 0x9f, 0xd5, 0xe6, 0x1f, 0xf5, 0x94, 0x3e, 0xab, + 0xfd, 0x36, 0xb0, 0xd1, 0xdc, 0xa7, 0xc7, 0xf1, 0x24, 0xb2, 0x2a, 0x91, 0x29, 0x02, 0x13, 0xef, + 0x8a, 0xec, 0x0d, 0xd8, 0x5c, 0xa0, 0x26, 0x5b, 0xba, 0xac, 0x6d, 0xa4, 0x49, 0xd9, 0x03, 0xb8, + 0x24, 0xe6, 0x76, 0xaa, 0x6f, 0xc5, 0xd5, 0x53, 0x8e, 0x6a, 0x24, 0x3d, 0xac, 0xfe, 0x1a, 0x94, + 0xa2, 0x90, 0xb6, 0x17, 0x2b, 0xbb, 0x2b, 0xc6, 0x55, 0xfd, 0xdb, 0xeb, 0x50, 0x8e, 0x03, 0xdc, + 0xbe, 0xd1, 0xec, 0xa0, 0x2f, 0x95, 0x04, 0xa7, 0xb2, 0x88, 0x29, 0x21, 0x20, 0x1a, 0x29, 0x71, + 0x5b, 0x6b, 0xee, 0xdb, 0x91, 0xc6, 0xc6, 0x21, 0x47, 0xbe, 0x4d, 0x2f, 0xe0, 0xd8, 0xae, 0x74, + 0x49, 0xb4, 0xac, 0x95, 0x10, 0x40, 0x0b, 0xed, 0x1a, 0xd0, 0x6f, 0xe2, 0x8c, 0xbe, 0x34, 0x6e, + 0xbb, 0xa7, 0xc8, 0xf7, 0x9c, 0x2f, 0x8d, 0xd3, 0x77, 0x56, 0x78, 0x74, 0x0d, 0x8f, 0x29, 0x88, + 0x3e, 0x56, 0xf8, 0x0a, 0x94, 0xe7, 0xf1, 0xd7, 0x2e, 0xc5, 0x8c, 0x98, 0x47, 0xdf, 0xba, 0x4c, + 0x8f, 0x6a, 0x79, 0x71, 0x54, 0x17, 0xe7, 0x34, 0x2c, 0xcd, 0x69, 0x35, 0x84, 0xa2, 0x08, 0xf2, + 0x7b, 0x71, 0x87, 0xbf, 0xb0, 0xab, 0x14, 0xc8, 0x1a, 0x4e, 0x74, 0x33, 0x15, 0x7f, 0x2e, 0x54, + 0x2c, 0xb7, 0x50, 0x31, 0xf5, 0xaf, 0xae, 0x03, 0x24, 0xc1, 0x82, 0xec, 0x9d, 0x85, 0xc0, 0xe4, + 0xcc, 0xd2, 0xb6, 0xbf, 0x10, 0x8f, 0xbc, 0xf0, 0x24, 0xd4, 0xfa, 0x37, 0x78, 0x12, 0xea, 0x21, + 0xd4, 0x02, 0x7f, 0xf4, 0x52, 0x87, 0x7b, 0x25, 0xf0, 0x47, 0xb1, 0xbf, 0xfd, 0x5d, 0xc0, 0x24, + 0x3d, 0x17, 0x99, 0x18, 0xaa, 0x4b, 0x5a, 0x4b, 0x39, 0xf0, 0x47, 0xfd, 0xe3, 0xaf, 0x5a, 0xfc, + 0x8a, 0x9c, 0x19, 0x84, 0xfa, 0x2a, 0x29, 0xb1, 0x69, 0x06, 0x61, 0x4b, 0x16, 0x14, 0x77, 0x60, + 0x03, 0x69, 0x97, 0x84, 0x45, 0xd5, 0x0c, 0x92, 0x03, 0x16, 0xf5, 0xf7, 0xa2, 0x23, 0xe9, 0x05, + 0x5f, 0x2e, 0xfb, 0x48, 0x18, 0xe2, 0x92, 0x12, 0x51, 0x5f, 0xe5, 0xfa, 0xe5, 0x0f, 0x58, 0xc5, + 0xa4, 0xcb, 0x1f, 0x2e, 0x5c, 0xff, 0xa6, 0x1f, 0x2e, 0xdc, 0x01, 0x48, 0x1e, 0xec, 0xc4, 0x15, + 0x18, 0x5f, 0xd6, 0x29, 0xf3, 0x6b, 0x38, 0xf7, 0x0f, 0xe0, 0xd2, 0x8a, 0xaf, 0xb8, 0xb3, 0x4b, + 0xb0, 0xd9, 0x78, 0xfc, 0x58, 0x6f, 0xf6, 0x7b, 0x7b, 0x9d, 0xc7, 0xba, 0xb8, 0x96, 0xf3, 0x1a, + 0xbc, 0x2a, 0x01, 0x1f, 0x6b, 0xfd, 0xa3, 0x43, 0x4c, 0x34, 0x1b, 0x43, 0xf1, 0x1d, 0xf5, 0xcc, + 0xfd, 0xd7, 0xa0, 0x2a, 0x7f, 0x63, 0x99, 0xee, 0xf4, 0x78, 0xae, 0xc5, 0xbf, 0x21, 0xd3, 0xfd, + 0xc9, 0x07, 0x4a, 0xe6, 0xbe, 0x0a, 0x15, 0xe9, 0x0b, 0x4e, 0x48, 0xb1, 0x6f, 0x04, 0x27, 0xe2, + 0x7b, 0x22, 0x86, 0x3b, 0xb1, 0x94, 0xcc, 0xfd, 0xbb, 0xa8, 0xc3, 0xcb, 0xdf, 0x4f, 0x02, 0x28, + 0xf4, 0x3c, 0x7f, 0x6a, 0x38, 0x82, 0xce, 0x9a, 0x07, 0x48, 0xf7, 0x2e, 0x5c, 0x5e, 0xf9, 0x35, + 0x28, 0xba, 0x11, 0x66, 0x4f, 0x67, 0x8e, 0xc5, 0xef, 0x36, 0xed, 0x5f, 0x1c, 0xfb, 0xb6, 0xa9, + 0x64, 0xee, 0x7f, 0xb2, 0xf0, 0xb5, 0x90, 0xa3, 0xde, 0x6e, 0xff, 0xa8, 0xd7, 0x6a, 0xb7, 0xf8, + 0xad, 0xa3, 0x4e, 0xaf, 0xd9, 0x3d, 0x1a, 0x74, 0x9e, 0x88, 0xad, 0xb5, 0xfd, 0x45, 0x94, 0x5c, + 0xbf, 0xbf, 0x1f, 0x3d, 0xee, 0x10, 0xd5, 0xba, 0xdb, 0x6f, 0xb4, 0xf8, 0x96, 0x1c, 0x3f, 0xef, + 0x34, 0xdc, 0xe5, 0x5f, 0x19, 0xd1, 0xda, 0x83, 0xa3, 0xee, 0x30, 0x7a, 0x4a, 0x6a, 0x03, 0xa0, + 0xd3, 0x6c, 0xef, 0xb6, 0xb5, 0xc7, 0x48, 0x90, 0xbd, 0xff, 0x43, 0xa8, 0x3f, 0xef, 0xba, 0x10, + 0xb6, 0xad, 0xb9, 0xdf, 0xa0, 0x2b, 0x59, 0xb8, 0x25, 0xf7, 0x75, 0x9e, 0x22, 0xc7, 0xa1, 0xd6, + 0xee, 0xb6, 0x29, 0xa6, 0xf6, 0xfe, 0x4f, 0x33, 0x92, 0x7a, 0x1a, 0x5d, 0xf9, 0x88, 0x01, 0xa2, + 0xc3, 0x65, 0x90, 0x66, 0x19, 0xa6, 0x92, 0x61, 0x57, 0x80, 0xa5, 0x40, 0x5d, 0x6f, 0x64, 0x38, + 0xca, 0x3a, 0x45, 0xcf, 0x46, 0x70, 0xba, 0x1a, 0xa8, 0x64, 0xd9, 0xab, 0x70, 0x2d, 0x86, 0x75, + 0xbd, 0xb3, 0x43, 0xdf, 0xf6, 0x7c, 0x3b, 0xbc, 0xe0, 0xe8, 0xdc, 0xfd, 0xff, 0x53, 0x9c, 0xfd, + 0xa6, 0x26, 0x2d, 0x16, 0xd0, 0x30, 0xcd, 0x04, 0x46, 0x72, 0x52, 0x59, 0x63, 0x57, 0xe1, 0x12, + 0x6d, 0x12, 0x0b, 0x88, 0x0c, 0x7b, 0x05, 0xae, 0x46, 0x36, 0xf4, 0x22, 0x72, 0x1d, 0x91, 0x9a, + 0x45, 0x91, 0x97, 0x4b, 0xc8, 0xec, 0xee, 0x0f, 0xfe, 0xe4, 0x17, 0x37, 0x33, 0xff, 0xe4, 0x17, + 0x37, 0x33, 0xff, 0xf6, 0x17, 0x37, 0xd7, 0xfe, 0xf0, 0xdf, 0xdf, 0xcc, 0xfc, 0xf8, 0x9d, 0x89, + 0x1d, 0x9e, 0xcc, 0x8f, 0x1f, 0x8c, 0xbc, 0xe9, 0xbb, 0x53, 0x23, 0xf4, 0xed, 0x73, 0xbe, 0x5b, + 0x45, 0x09, 0xd7, 0x7a, 0x77, 0x76, 0x3a, 0x79, 0x77, 0x76, 0xfc, 0x2e, 0xae, 0x9b, 0xe3, 0xc2, + 0xcc, 0xf7, 0x42, 0xef, 0xd1, 0xff, 0x0a, 0x00, 0x00, 0xff, 0xff, 0xf4, 0x4a, 0x2d, 0x1d, 0xc5, + 0x8b, 0x00, 0x00, } func (m *Type) Marshal() (dAtA []byte, err error) { @@ -18925,6 +18934,16 @@ func (m *UpdateCtx) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if m.IgnoreAffectedRows { + i-- + if m.IgnoreAffectedRows { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x68 + } if m.CountDeleteAffectRows { i-- if m.CountDeleteAffectRows { @@ -28671,6 +28690,9 @@ func (m *UpdateCtx) ProtoSize() (n int) { if m.CountDeleteAffectRows { n += 2 } + if m.IgnoreAffectedRows { + n += 2 + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -42527,6 +42549,26 @@ func (m *UpdateCtx) Unmarshal(dAtA []byte) error { } } m.CountDeleteAffectRows = bool(v != 0) + case 13: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IgnoreAffectedRows", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPlan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.IgnoreAffectedRows = bool(v != 0) default: iNdEx = preIndex skippy, err := skipPlan(dAtA[iNdEx:]) diff --git a/pkg/sql/colexec/multi_update/affect_rows_test.go b/pkg/sql/colexec/multi_update/affect_rows_test.go index 659ad558fbddd..8f9571677885f 100644 --- a/pkg/sql/colexec/multi_update/affect_rows_test.go +++ b/pkg/sql/colexec/multi_update/affect_rows_test.go @@ -78,6 +78,14 @@ func TestUpsertAffectRowsAccounting(t *testing.T) { require.EqualValues(t, 0, op.GetAffectedRows()) }) + t.Run("foreign key side effects never affect rows", func(t *testing.T) { + op := newAffectRowsTestOp(actionUpdate, true) + op.MultiUpdateCtx = []*MultiUpdateCtx{{IgnoreAffectedRows: true}} + op.addInsertAffectRows(UpdateMainTable, 4) + op.addDeleteAffectRows(UpdateMainTable, 3) + require.EqualValues(t, 0, op.GetAffectedRows()) + }) + t.Run("batch upsert: new + updated rows", func(t *testing.T) { // 2 brand new rows (INSERT only) + 3 conflicting rows (DELETE + INSERT): // inserts cover all 5 rows, deletes cover the 3 conflicts => 5 + 3 = 8. diff --git a/pkg/sql/colexec/multi_update/multi_update_partition.go b/pkg/sql/colexec/multi_update/multi_update_partition.go index 247ee15bfd10f..843c372858f5c 100644 --- a/pkg/sql/colexec/multi_update/multi_update_partition.go +++ b/pkg/sql/colexec/multi_update/multi_update_partition.go @@ -443,6 +443,7 @@ func (ctx *MultiUpdateCtx) clone() *MultiUpdateCtx { PartitionCols: ctx.PartitionCols, SkipInsertOnNullPk: ctx.SkipInsertOnNullPk, InsertPkColIdx: ctx.InsertPkColIdx, + IgnoreAffectedRows: ctx.IgnoreAffectedRows, } objRef := *ctx.ObjRef def := *ctx.TableDef diff --git a/pkg/sql/colexec/multi_update/types.go b/pkg/sql/colexec/multi_update/types.go index b9dcaacb7a48b..9828987e9a784 100644 --- a/pkg/sql/colexec/multi_update/types.go +++ b/pkg/sql/colexec/multi_update/types.go @@ -116,7 +116,8 @@ type MultiUpdateCtx struct { SkipInsertOnNullPk bool // InsertPkColIdx is the PK column's index within InsertCols. It is only // used with SkipInsertOnNullPk for REPLACE delete-only rows. - InsertPkColIdx int + InsertPkColIdx int + IgnoreAffectedRows bool } func (update MultiUpdate) TypeName() string { @@ -230,5 +231,8 @@ func (update *MultiUpdate) addDeleteAffectRows(tableType UpdateTableType, rowCou } func (update *MultiUpdate) doAddAffectedRows(affectedRows uint64) { + if len(update.MultiUpdateCtx) > 0 && update.MultiUpdateCtx[0].IgnoreAffectedRows { + return + } update.ctr.affectedRows += affectedRows } diff --git a/pkg/sql/compile/operator.go b/pkg/sql/compile/operator.go index 1d3b21a7efeb9..dfd0fae3bd994 100644 --- a/pkg/sql/compile/operator.go +++ b/pkg/sql/compile/operator.go @@ -896,6 +896,7 @@ func constructMultiUpdate( PartitionCols: partitionCols, SkipInsertOnNullPk: updateCtx.SkipInsertOnNullPk, InsertPkColIdx: int(updateCtx.InsertPkColIdx), + IgnoreAffectedRows: updateCtx.IgnoreAffectedRows, } } arg.Action = action diff --git a/pkg/sql/compile/remoterun.go b/pkg/sql/compile/remoterun.go index 344ab66a532ed..57222d49a4328 100644 --- a/pkg/sql/compile/remoterun.go +++ b/pkg/sql/compile/remoterun.go @@ -871,6 +871,7 @@ func convertToPipelineInstruction(op vm.Operator, proc *process.Process, ctx *sc TableDef: muCtx.TableDef, SkipInsertOnNullPk: muCtx.SkipInsertOnNullPk, InsertPkColIdx: int32(muCtx.InsertPkColIdx), + IgnoreAffectedRows: muCtx.IgnoreAffectedRows, CountDeleteAffectRows: t.CountDeleteAffectRows, } @@ -1380,6 +1381,7 @@ func convertToVmOperator(opr *pipeline.Instruction, ctx *scopeContext, eng engin TableDef: muCtx.TableDef, SkipInsertOnNullPk: muCtx.SkipInsertOnNullPk, InsertPkColIdx: int(muCtx.InsertPkColIdx), + IgnoreAffectedRows: muCtx.IgnoreAffectedRows, } arg.MultiUpdateCtx[i].InsertCols = make([]int, len(muCtx.InsertCols)) diff --git a/pkg/sql/compile/remoterun_test.go b/pkg/sql/compile/remoterun_test.go index ec24e5f6581a0..3db9926448ff3 100644 --- a/pkg/sql/compile/remoterun_test.go +++ b/pkg/sql/compile/remoterun_test.go @@ -684,8 +684,9 @@ func Test_DMLOperatorSerializationRoundtrip(t *testing.T) { op := &multi_update.MultiUpdate{ MultiUpdateCtx: []*multi_update.MultiUpdateCtx{ { - ObjRef: &plan.ObjectRef{ObjName: "t1"}, - TableDef: &plan.TableDef{Name: "t1"}, + ObjRef: &plan.ObjectRef{ObjName: "t1"}, + TableDef: &plan.TableDef{Name: "t1"}, + IgnoreAffectedRows: true, }, }, Action: multi_update.UpdateWriteTable, @@ -695,12 +696,16 @@ func Test_DMLOperatorSerializationRoundtrip(t *testing.T) { require.NoError(t, err) require.True(t, pipeInstr.MultiUpdate.UpdateCtxList[0].CountDeleteAffectRows, "serialized UpdateCtx must carry CountDeleteAffectRows") + require.True(t, pipeInstr.MultiUpdate.UpdateCtxList[0].IgnoreAffectedRows, + "serialized UpdateCtx must carry IgnoreAffectedRows") restored, err := convertToVmOperator(pipeInstr, ctx, nil) require.NoError(t, err) restoredOp := restored.(*multi_update.MultiUpdate) require.True(t, restoredOp.CountDeleteAffectRows, "CountDeleteAffectRows must survive the remote pipeline round-trip") + require.True(t, restoredOp.MultiUpdateCtx[0].IgnoreAffectedRows, + "IgnoreAffectedRows must survive the remote pipeline round-trip") }) t.Run("MultiUpdate_RejectZeroTemporal", func(t *testing.T) { diff --git a/pkg/sql/plan/bind_update_fk.go b/pkg/sql/plan/bind_update_fk.go index 4770654c1ba41..40c9a55717977 100644 --- a/pkg/sql/plan/bind_update_fk.go +++ b/pkg/sql/plan/bind_update_fk.go @@ -15,6 +15,10 @@ package plan import ( + "fmt" + "sort" + + "github.com/matrixorigin/matrixone/pkg/catalog" "github.com/matrixorigin/matrixone/pkg/common/moerr" "github.com/matrixorigin/matrixone/pkg/pb/plan" ) @@ -41,15 +45,18 @@ func (builder *QueryBuilder) appendUpdateForeignKeyChecks( } alias := dmlCtx.aliases[i] - if err = builder.rejectUpdateOfReferencedParentKey( + lastNodeID, selectNodeTag, err = builder.appendUpdateParentForeignKeyChecks( bindCtx, tableDef, alias, + lastNodeID, + selectNodeTag, + oldColName2Idx, newColName2Idx, - ); err != nil { + ) + if err != nil { return 0, 0, nil, err } - affectedFks := affectedUpdateChildFks(tableDef, alias, newColName2Idx) if len(affectedFks) == 0 { continue @@ -185,14 +192,23 @@ func affectedUpdateChildFks( return affected } -func (builder *QueryBuilder) rejectUpdateOfReferencedParentKey( +type updateParentForeignKey struct { + childObjRef *plan.ObjectRef + childTableDef *plan.TableDef + fk *plan.ForeignKeyDef +} + +func (builder *QueryBuilder) appendUpdateParentForeignKeyChecks( bindCtx *BindContext, tableDef *plan.TableDef, alias string, + lastNodeID int32, + selectNodeTag int32, + oldColName2Idx map[string]int32, newColName2Idx map[string]int32, -) error { +) (int32, int32, error) { if tableDef == nil || len(tableDef.RefChildTbls) == 0 { - return nil + return lastNodeID, selectNodeTag, nil } parentColIDToName := make(map[uint64]string, len(tableDef.Cols)) @@ -200,6 +216,7 @@ func (builder *QueryBuilder) rejectUpdateOfReferencedParentKey( parentColIDToName[col.ColId] = col.Name } + affected := make([]updateParentForeignKey, 0) visited := make(map[uint64]struct{}, len(tableDef.RefChildTbls)) for _, childTableID := range tableDef.RefChildTbls { if childTableID == 0 { @@ -210,12 +227,12 @@ func (builder *QueryBuilder) rejectUpdateOfReferencedParentKey( } visited[childTableID] = struct{}{} - _, childTableDef, err := builder.compCtx.ResolveById(childTableID, bindCtx.snapshot) + childObjRef, childTableDef, err := builder.compCtx.ResolveById(childTableID, bindCtx.snapshot) if err != nil { - return err + return 0, 0, err } if childTableDef == nil { - return moerr.NewInternalErrorf( + return 0, 0, moerr.NewInternalErrorf( builder.GetContext(), "foreign-key child table %d not found", childTableID, @@ -230,12 +247,167 @@ func (builder *QueryBuilder) rejectUpdateOfReferencedParentKey( } for _, parentColID := range fk.ForeignCols { if _, ok := newColName2Idx[alias+"."+parentColIDToName[parentColID]]; ok { - return newLegacyUpdatePlannerRouteError( - updateRouteReasonForeignKey, - moerr.NewUnsupportedDML( - builder.GetContext(), - foreignKeyUnsupportedDMLCause, - ), + affected = append(affected, updateParentForeignKey{ + childObjRef: childObjRef, + childTableDef: childTableDef, + fk: fk, + }) + break + } + } + } + } + sort.SliceStable(affected, func(i, j int) bool { + if affected[i].childTableDef.TblId != affected[j].childTableDef.TblId { + return affected[i].childTableDef.TblId < affected[j].childTableDef.TblId + } + return affected[i].fk.Name < affected[j].fk.Name + }) + + for _, affectedFK := range affected { + switch affectedFK.fk.OnUpdate { + case plan.ForeignKeyDef_RESTRICT, + plan.ForeignKeyDef_NO_ACTION, + plan.ForeignKeyDef_SET_DEFAULT: + var err error + lastNodeID, selectNodeTag, err = builder.appendUpdateParentRestrictCheck( + bindCtx, + tableDef, + alias, + affectedFK, + lastNodeID, + selectNodeTag, + oldColName2Idx, + newColName2Idx, + ) + if err != nil { + return 0, 0, err + } + case plan.ForeignKeyDef_CASCADE, plan.ForeignKeyDef_SET_NULL: + default: + return 0, 0, moerr.NewInternalErrorf( + builder.GetContext(), + "unsupported foreign key ON UPDATE action %s", + affectedFK.fk.OnUpdate.String(), + ) + } + } + + mutations := make([]updateParentForeignKey, 0, len(affected)) + for _, affectedFK := range affected { + if affectedFK.fk.OnUpdate == plan.ForeignKeyDef_CASCADE || + affectedFK.fk.OnUpdate == plan.ForeignKeyDef_SET_NULL { + mutations = append(mutations, affectedFK) + } + } + if len(mutations) == 0 { + return lastNodeID, selectNodeTag, nil + } + + mutationByChild := make(map[uint64]struct{}, len(mutations)) + for _, mutation := range mutations { + if _, exists := mutationByChild[mutation.childTableDef.TblId]; exists { + return 0, 0, moerr.NewNotSupported( + builder.GetContext(), + "multiple parent foreign key actions targeting the same child table", + ) + } + mutationByChild[mutation.childTableDef.TblId] = struct{}{} + if err := builder.validateModernUpdateParentMutation(bindCtx, tableDef, mutation); err != nil { + return 0, 0, err + } + } + + sourceSinkID := appendSinkNodeWithTag(builder, bindCtx, lastNodeID, selectNodeTag) + sourceStep := builder.appendStep(sourceSinkID) + for _, mutation := range mutations { + if err := builder.appendUpdateParentMutation( + bindCtx, + tableDef, + alias, + mutation, + sourceStep, + selectNodeTag, + oldColName2Idx, + newColName2Idx, + ); err != nil { + return 0, 0, err + } + } + lastNodeID = builder.appendTaggedSinkScan(bindCtx, sourceStep, selectNodeTag) + return lastNodeID, selectNodeTag, nil +} + +func (builder *QueryBuilder) validateModernUpdateParentMutation( + bindCtx *BindContext, + parentTableDef *plan.TableDef, + affectedFK updateParentForeignKey, +) error { + childTableDef := affectedFK.childTableDef + ensureName2ColIndexForReplace(childTableDef) + if childTableDef.TblId == parentTableDef.TblId { + return moerr.NewNotSupported(builder.GetContext(), "self-referencing parent foreign key action") + } + _, hasIrregularIndex := getValidIndexes(childTableDef) + if hasIrregularIndex { + return moerr.NewNotSupported( + builder.GetContext(), + "parent foreign key action on child table with irregular indexes", + ) + } + if childTableDef.Partition != nil { + return moerr.NewNotSupported( + builder.GetContext(), + "parent foreign key action on partitioned child table", + ) + } + + childColIDToName := make(map[uint64]string, len(childTableDef.Cols)) + for _, col := range childTableDef.Cols { + childColIDToName[col.ColId] = col.Name + } + primaryNames := make(map[string]struct{}, len(childTableDef.Pkey.Names)+1) + primaryNames[childTableDef.Pkey.PkeyColName] = struct{}{} + for _, name := range childTableDef.Pkey.Names { + primaryNames[name] = struct{}{} + } + for _, childColID := range affectedFK.fk.Cols { + if _, ok := primaryNames[childColIDToName[childColID]]; ok { + return moerr.NewNotSupported( + builder.GetContext(), + "parent foreign key action changing child primary key", + ) + } + } + + updatedChildCols := make(map[uint64]struct{}, len(affectedFK.fk.Cols)) + for _, childColID := range affectedFK.fk.Cols { + updatedChildCols[childColID] = struct{}{} + } + visited := make(map[uint64]struct{}, len(childTableDef.RefChildTbls)) + for _, grandchildID := range childTableDef.RefChildTbls { + if grandchildID == 0 { + grandchildID = childTableDef.TblId + } + if _, ok := visited[grandchildID]; ok { + continue + } + visited[grandchildID] = struct{}{} + _, grandchildDef, err := builder.compCtx.ResolveById(grandchildID, bindCtx.snapshot) + if err != nil { + return err + } + for _, fk := range grandchildDef.Fkeys { + referencesChild := fk.ForeignTbl == childTableDef.TblId || + (fk.ForeignTbl == 0 && grandchildDef.TblId == childTableDef.TblId) + if !referencesChild { + continue + } + for _, referencedColID := range fk.ForeignCols { + if _, changed := updatedChildCols[referencedColID]; changed { + return moerr.NewNotSupported( + builder.GetContext(), + "recursive parent foreign key action graph", ) } } @@ -244,6 +416,641 @@ func (builder *QueryBuilder) rejectUpdateOfReferencedParentKey( return nil } +func (builder *QueryBuilder) appendUpdateParentMutation( + bindCtx *BindContext, + parentTableDef *plan.TableDef, + parentAlias string, + affectedFK updateParentForeignKey, + sourceStep int32, + sourceTag int32, + oldColName2Idx map[string]int32, + newColName2Idx map[string]int32, +) error { + parentNodeID := builder.appendTaggedSinkScan(bindCtx, sourceStep, sourceTag) + parentNode := builder.qry.Nodes[parentNodeID] + unchanged, err := builder.buildUpdateReferencedKeyUnchangedExpr( + parentTableDef, + affectedFK.fk, + parentAlias, + sourceTag, + oldColName2Idx, + newColName2Idx, + parentNode, + ) + if err != nil { + return err + } + if unchanged != nil { + changed, bindErr := BindFuncExprImplByPlanExpr( + builder.GetContext(), + "not", + []*plan.Expr{unchanged}, + ) + if bindErr != nil { + return bindErr + } + parentNodeID = builder.appendNode(&plan.Node{ + NodeType: plan.Node_FILTER, + Children: []int32{parentNodeID}, + FilterList: []*plan.Expr{changed}, + }, bindCtx) + } + + childTableDef := affectedFK.childTableDef + childTag := builder.genNewBindTag() + builder.addNameByColRef(childTag, childTableDef) + childNodeID := builder.appendNode(&plan.Node{ + NodeType: plan.Node_TABLE_SCAN, + TableDef: childTableDef, + ObjRef: affectedFK.childObjRef, + BindingTags: []int32{childTag}, + ScanSnapshot: bindCtx.snapshot, + }, bindCtx) + + parentColIDToName := make(map[uint64]string, len(parentTableDef.Cols)) + for _, col := range parentTableDef.Cols { + parentColIDToName[col.ColId] = col.Name + } + childColIDToPos := make(map[uint64]int32, len(childTableDef.Cols)) + for i, col := range childTableDef.Cols { + childColIDToPos[col.ColId] = int32(i) + } + + joinPreds := make([]*plan.Expr, len(affectedFK.fk.Cols)) + newChildExprs := make(map[int32]*plan.Expr, len(affectedFK.fk.Cols)) + for i, childColID := range affectedFK.fk.Cols { + parentColName := parentColIDToName[affectedFK.fk.ForeignCols[i]] + oldParentPos := oldColName2Idx[parentAlias+"."+parentColName] + newParentPos := oldParentPos + if pos, ok := newColName2Idx[parentAlias+"."+parentColName]; ok { + newParentPos = pos + } + childPos := childColIDToPos[childColID] + oldParentExpr := &plan.Expr{ + Typ: parentNode.ProjectList[oldParentPos].Typ, + Expr: &plan.Expr_Col{Col: &plan.ColRef{ + RelPos: sourceTag, + ColPos: oldParentPos, + }}, + } + childExpr := &plan.Expr{ + Typ: childTableDef.Cols[childPos].Typ, + Expr: &plan.Expr_Col{Col: &plan.ColRef{ + RelPos: childTag, + ColPos: childPos, + }}, + } + joinPreds[i], err = BindFuncExprImplByPlanExpr( + builder.GetContext(), + "=", + []*plan.Expr{oldParentExpr, childExpr}, + ) + if err != nil { + return err + } + + switch affectedFK.fk.OnUpdate { + case plan.ForeignKeyDef_CASCADE: + newChildExprs[childPos] = &plan.Expr{ + Typ: childTableDef.Cols[childPos].Typ, + Expr: &plan.Expr_Col{Col: &plan.ColRef{ + RelPos: sourceTag, + ColPos: newParentPos, + }}, + } + case plan.ForeignKeyDef_SET_NULL: + col := childTableDef.Cols[childPos] + if col.Default != nil && !col.Default.NullAbility { + return moerr.NewConstraintViolation( + builder.GetContext(), + fmt.Sprintf("Column '%s' cannot be null", col.Name), + ) + } + newChildExprs[childPos] = &plan.Expr{ + Typ: col.Typ, + Expr: &plan.Expr_Lit{Lit: &Const{Isnull: true}}, + } + } + } + + joinNodeID := builder.appendNode(&plan.Node{ + NodeType: plan.Node_JOIN, + Children: []int32{parentNodeID, childNodeID}, + JoinType: plan.Node_INNER, + OnList: joinPreds, + }, bindCtx) + + type mutationIndex struct { + def *plan.IndexDef + objRef *plan.ObjectRef + tableDef *plan.TableDef + tag int32 + } + indexes := make([]mutationIndex, 0, len(childTableDef.Indexes)) + for _, idxDef := range childTableDef.Indexes { + if !catalog.IsRegularIndexAlgo(idxDef.IndexAlgo) { + continue + } + idxObjRef, idxTableDef, resolveErr := builder.compCtx.ResolveIndexTableByRef( + affectedFK.childObjRef, + idxDef.IndexTableName, + bindCtx.snapshot, + ) + if resolveErr != nil { + return resolveErr + } + ensureName2ColIndexForReplace(idxTableDef) + idxTag := builder.genNewBindTag() + builder.addNameByColRef(idxTag, idxTableDef) + idxScanID := builder.appendNode(&plan.Node{ + NodeType: plan.Node_TABLE_SCAN, + TableDef: idxTableDef, + ObjRef: idxObjRef, + BindingTags: []int32{idxTag}, + ScanSnapshot: bindCtx.snapshot, + }, bindCtx) + + oldKeyExpr, buildErr := builder.buildUpdateMutationIndexKey( + childTableDef, + idxDef, + childTag, + nil, + ) + if buildErr != nil { + return buildErr + } + lookupName := indexLookupColumnName(idxDef) + lookupPos := idxTableDef.Name2ColIndex[lookupName] + storedKeyExpr := &plan.Expr{ + Typ: idxTableDef.Cols[lookupPos].Typ, + Expr: &plan.Expr_Col{Col: &plan.ColRef{ + RelPos: idxTag, + ColPos: lookupPos, + }}, + } + indexMatch, buildErr := BindFuncExprImplByPlanExpr( + builder.GetContext(), + "=", + []*plan.Expr{oldKeyExpr, storedKeyExpr}, + ) + if buildErr != nil { + return buildErr + } + joinType := plan.Node_INNER + if idxDef.Unique { + joinType = plan.Node_LEFT + } + joinNodeID = builder.appendNode(&plan.Node{ + NodeType: plan.Node_JOIN, + Children: []int32{joinNodeID, idxScanID}, + JoinType: joinType, + OnList: []*plan.Expr{indexMatch}, + }, bindCtx) + indexes = append(indexes, mutationIndex{ + def: idxDef, + objRef: idxObjRef, + tableDef: idxTableDef, + tag: idxTag, + }) + } + + actionTag := builder.genNewBindTag() + actionProjection := make([]*plan.Expr, len(childTableDef.Cols)) + for i, col := range childTableDef.Cols { + if newExpr, ok := newChildExprs[int32(i)]; ok { + actionProjection[i] = newExpr + continue + } + actionProjection[i] = &plan.Expr{ + Typ: col.Typ, + Expr: &plan.Expr_Col{Col: &plan.ColRef{ + RelPos: childTag, + ColPos: int32(i), + }}, + } + } + for i, col := range childTableDef.Cols { + if col.GeneratedCol == nil { + continue + } + generatedExpr := builder.applyGeneratedColumnAssignmentCast( + DeepCopyExpr(col.GeneratedCol.Expr), + false, + ) + actionProjection[i] = substituteColRefsInExpr( + generatedExpr, + actionProjection, + 0, + ) + } + finalReplacements := make(map[int32]*plan.Expr, len(newChildExprs)) + for pos, expr := range newChildExprs { + finalReplacements[pos] = expr + } + for i, col := range childTableDef.Cols { + if col.GeneratedCol != nil { + finalReplacements[int32(i)] = actionProjection[i] + } + } + + type mutationIndexPositions struct { + oldRowID int32 + oldKey int32 + newKey int32 + } + indexPositions := make([]mutationIndexPositions, len(indexes)) + for i, idx := range indexes { + oldRowIDPos := idx.tableDef.Name2ColIndex[catalog.Row_ID] + oldKeyPos := idx.tableDef.Name2ColIndex[indexLookupColumnName(idx.def)] + indexPositions[i].oldRowID = int32(len(actionProjection)) + actionProjection = append(actionProjection, &plan.Expr{ + Typ: idx.tableDef.Cols[oldRowIDPos].Typ, + Expr: &plan.Expr_Col{Col: &plan.ColRef{ + RelPos: idx.tag, + ColPos: oldRowIDPos, + }}, + }) + indexPositions[i].oldKey = int32(len(actionProjection)) + actionProjection = append(actionProjection, &plan.Expr{ + Typ: idx.tableDef.Cols[oldKeyPos].Typ, + Expr: &plan.Expr_Col{Col: &plan.ColRef{ + RelPos: idx.tag, + ColPos: oldKeyPos, + }}, + }) + newKeyExpr, buildErr := builder.buildUpdateMutationIndexKey( + childTableDef, + idx.def, + childTag, + finalReplacements, + ) + if buildErr != nil { + return buildErr + } + indexPositions[i].newKey = int32(len(actionProjection)) + actionProjection = append(actionProjection, newKeyExpr) + } + actionNodeID := builder.appendNode(&plan.Node{ + NodeType: plan.Node_PROJECT, + Children: []int32{joinNodeID}, + ProjectList: actionProjection, + BindingTags: []int32{actionTag}, + }, bindCtx) + + rowIDPos := childTableDef.Name2ColIndex[catalog.Row_ID] + pkPos := childTableDef.Name2ColIndex[childTableDef.Pkey.PkeyColName] + insertCols := make([]plan.ColRef, 0, len(childTableDef.Cols)-1) + for i, col := range childTableDef.Cols { + if col.Name == catalog.Row_ID { + continue + } + insertCols = append(insertCols, plan.ColRef{RelPos: actionTag, ColPos: int32(i)}) + } + lockTarget := &plan.LockTarget{ + TableId: childTableDef.TblId, + ObjRef: affectedFK.childObjRef, + PrimaryColIdxInBat: pkPos, + PrimaryColRelPos: actionTag, + PrimaryColTyp: actionProjection[pkPos].Typ, + } + lockTargets := []*plan.LockTarget{lockTarget} + updateCtxList := []*plan.UpdateCtx{{ + ObjRef: affectedFK.childObjRef, + TableDef: childTableDef, + InsertCols: insertCols, + IgnoreAffectedRows: true, + DeleteCols: []plan.ColRef{ + {RelPos: actionTag, ColPos: rowIDPos}, + {RelPos: actionTag, ColPos: pkPos}, + }, + }} + for i, idx := range indexes { + positions := indexPositions[i] + updateCtxList = append(updateCtxList, &plan.UpdateCtx{ + ObjRef: idx.objRef, + TableDef: idx.tableDef, + IgnoreAffectedRows: true, + InsertCols: []plan.ColRef{ + {RelPos: actionTag, ColPos: positions.newKey}, + {RelPos: actionTag, ColPos: pkPos}, + }, + DeleteCols: []plan.ColRef{ + {RelPos: actionTag, ColPos: positions.oldRowID}, + {RelPos: actionTag, ColPos: positions.oldKey}, + }, + }) + if idx.def.Unique { + lockTargets = append(lockTargets, + &plan.LockTarget{ + TableId: idx.tableDef.TblId, + ObjRef: idx.objRef, + PrimaryColIdxInBat: positions.oldKey, + PrimaryColRelPos: actionTag, + PrimaryColTyp: actionProjection[positions.oldKey].Typ, + }, + &plan.LockTarget{ + TableId: idx.tableDef.TblId, + ObjRef: idx.objRef, + PrimaryColIdxInBat: positions.newKey, + PrimaryColRelPos: actionTag, + PrimaryColTyp: actionProjection[positions.newKey].Typ, + }, + ) + } + } + sort.SliceStable(lockTargets, func(i, j int) bool { + if lockTargets[i].TableId != lockTargets[j].TableId { + return lockTargets[i].TableId < lockTargets[j].TableId + } + if lockTargets[i].PrimaryColIdxInBat != lockTargets[j].PrimaryColIdxInBat { + return lockTargets[i].PrimaryColIdxInBat < lockTargets[j].PrimaryColIdxInBat + } + return lockTargets[i].PrimaryColRelPos < lockTargets[j].PrimaryColRelPos + }) + actionNodeID = builder.appendNode(&plan.Node{ + NodeType: plan.Node_LOCK_OP, + Children: []int32{actionNodeID}, + TableDef: childTableDef, + BindingTags: []int32{actionTag}, + LockTargets: lockTargets, + }, bindCtx) + actionNodeID = builder.appendNode(&plan.Node{ + NodeType: plan.Node_MULTI_UPDATE, + Children: []int32{actionNodeID}, + BindingTags: []int32{builder.genNewBindTag()}, + UpdateCtxList: updateCtxList, + }, bindCtx) + builder.appendStep(actionNodeID) + builder.qry.HasForeignKeyAction = true + return nil +} + +func (builder *QueryBuilder) buildUpdateMutationIndexKey( + tableDef *plan.TableDef, + idxDef *plan.IndexDef, + tableTag int32, + replacements map[int32]*plan.Expr, +) (*plan.Expr, error) { + if isSpatialIndexDef(idxDef) { + pkPos := tableDef.Name2ColIndex[tableDef.Pkey.PkeyColName] + if replacement, ok := replacements[pkPos]; ok { + return DeepCopyExpr(replacement), nil + } + return &plan.Expr{ + Typ: tableDef.Cols[pkPos].Typ, + Expr: &plan.Expr_Col{Col: &plan.ColRef{ + RelPos: tableTag, + ColPos: pkPos, + }}, + }, nil + } + + prefixLengths, err := catalog.IndexPrefixLengthsFromParamsWithError(idxDef.IndexAlgoParams) + if err != nil { + return nil, err + } + args := make([]*plan.Expr, len(idxDef.Parts)) + for i, part := range idxDef.Parts { + colName := catalog.ResolveAlias(part) + colPos := tableDef.Name2ColIndex[colName] + inputExpr, ok := replacements[colPos] + if ok { + inputExpr = DeepCopyExpr(inputExpr) + } else { + inputExpr = &plan.Expr{ + Typ: tableDef.Cols[colPos].Typ, + Expr: &plan.Expr_Col{Col: &plan.ColRef{ + RelPos: tableTag, + ColPos: colPos, + }}, + } + } + args[i], err = builder.makeIndexPartExprFromInputExpr( + inputExpr, + colName, + prefixLengths, + ) + if err != nil { + return nil, err + } + } + if !indexTableStoresSerializedKey(idxDef) { + return args[0], nil + } + funcName := "serial" + if !idxDef.Unique { + funcName = "serial_full" + } + return BindFuncExprImplByPlanExpr(builder.GetContext(), funcName, args) +} + +func (builder *QueryBuilder) appendUpdateParentRestrictCheck( + bindCtx *BindContext, + parentTableDef *plan.TableDef, + parentAlias string, + affectedFK updateParentForeignKey, + lastNodeID int32, + selectNodeTag int32, + oldColName2Idx map[string]int32, + newColName2Idx map[string]int32, +) (int32, int32, error) { + sourceNode := builder.qry.Nodes[lastNodeID] + sourceTypes := make([]plan.Type, len(sourceNode.ProjectList)) + for i, expr := range sourceNode.ProjectList { + sourceTypes[i] = expr.Typ + } + + parentColIDToName := make(map[uint64]string, len(parentTableDef.Cols)) + for _, col := range parentTableDef.Cols { + parentColIDToName[col.ColId] = col.Name + } + childColIDToPos := make(map[uint64]int32, len(affectedFK.childTableDef.Cols)) + for i, col := range affectedFK.childTableDef.Cols { + childColIDToPos[col.ColId] = int32(i) + } + + childTag := builder.genNewBindTag() + builder.addNameByColRef(childTag, affectedFK.childTableDef) + childScanID := builder.appendNode(&plan.Node{ + NodeType: plan.Node_TABLE_SCAN, + TableDef: affectedFK.childTableDef, + ObjRef: affectedFK.childObjRef, + BindingTags: []int32{childTag}, + ScanSnapshot: bindCtx.snapshot, + }, bindCtx) + + joinPreds := make([]*plan.Expr, len(affectedFK.fk.Cols)) + for i, childColID := range affectedFK.fk.Cols { + parentColName := parentColIDToName[affectedFK.fk.ForeignCols[i]] + parentPos := oldColName2Idx[parentAlias+"."+parentColName] + childPos := childColIDToPos[childColID] + parentExpr := &plan.Expr{ + Typ: sourceNode.ProjectList[parentPos].Typ, + Expr: &plan.Expr_Col{Col: &plan.ColRef{ + RelPos: selectNodeTag, + ColPos: parentPos, + }}, + } + childExpr := &plan.Expr{ + Typ: affectedFK.childTableDef.Cols[childPos].Typ, + Expr: &plan.Expr_Col{Col: &plan.ColRef{ + RelPos: childTag, + ColPos: childPos, + }}, + } + var err error + joinPreds[i], err = BindFuncExprImplByPlanExpr( + builder.GetContext(), + "=", + []*plan.Expr{parentExpr, childExpr}, + ) + if err != nil { + return 0, 0, err + } + } + + markNodeID, markExpr, err := builder.insertMarkJoin( + lastNodeID, + childScanID, + joinPreds, + nil, + false, + bindCtx, + ) + if err != nil { + return 0, 0, err + } + + unchanged, err := builder.buildUpdateReferencedKeyUnchangedExpr( + parentTableDef, + affectedFK.fk, + parentAlias, + selectNodeTag, + oldColName2Idx, + newColName2Idx, + sourceNode, + ) + if err != nil { + return 0, 0, err + } + noChild, err := BindFuncExprImplByPlanExpr( + builder.GetContext(), + "not", + []*plan.Expr{markExpr}, + ) + if err != nil { + return 0, 0, err + } + ok := noChild + if unchanged != nil { + ok, err = BindFuncExprImplByPlanExpr( + builder.GetContext(), + "or", + []*plan.Expr{unchanged, noChild}, + ) + if err != nil { + return 0, 0, err + } + } + assertExpr, err := BindFuncExprImplByPlanExpr( + builder.GetContext(), + "assert", + []*plan.Expr{ + ok, + makePlan2StringConstExprWithType( + "Cannot delete or update a parent row: a foreign key constraint fails", + ), + }, + ) + if err != nil { + return 0, 0, err + } + lastNodeID = builder.appendNode(&plan.Node{ + NodeType: plan.Node_FILTER, + Children: []int32{markNodeID}, + FilterList: []*plan.Expr{assertExpr}, + }, bindCtx) + + validatedTag := builder.genNewBindTag() + projectList := make([]*plan.Expr, len(sourceTypes)) + for i, typ := range sourceTypes { + projectList[i] = &plan.Expr{ + Typ: typ, + Expr: &plan.Expr_Col{Col: &plan.ColRef{ + RelPos: selectNodeTag, + ColPos: int32(i), + }}, + } + } + lastNodeID = builder.appendNode(&plan.Node{ + NodeType: plan.Node_PROJECT, + ProjectList: projectList, + Children: []int32{lastNodeID}, + BindingTags: []int32{validatedTag}, + }, bindCtx) + return lastNodeID, validatedTag, nil +} + +func (builder *QueryBuilder) buildUpdateReferencedKeyUnchangedExpr( + parentTableDef *plan.TableDef, + fk *plan.ForeignKeyDef, + parentAlias string, + selectNodeTag int32, + oldColName2Idx map[string]int32, + newColName2Idx map[string]int32, + selectNode *plan.Node, +) (*plan.Expr, error) { + parentColIDToName := make(map[uint64]string, len(parentTableDef.Cols)) + for _, col := range parentTableDef.Cols { + parentColIDToName[col.ColId] = col.Name + } + + var unchanged *plan.Expr + for _, parentColID := range fk.ForeignCols { + qualifiedName := parentAlias + "." + parentColIDToName[parentColID] + newPos, updated := newColName2Idx[qualifiedName] + if !updated { + continue + } + oldPos := oldColName2Idx[qualifiedName] + oldExpr := &plan.Expr{ + Typ: selectNode.ProjectList[oldPos].Typ, + Expr: &plan.Expr_Col{Col: &plan.ColRef{ + RelPos: selectNodeTag, + ColPos: oldPos, + }}, + } + newExpr := &plan.Expr{ + Typ: selectNode.ProjectList[newPos].Typ, + Expr: &plan.Expr_Col{Col: &plan.ColRef{ + RelPos: selectNodeTag, + ColPos: newPos, + }}, + } + equal, err := BindFuncExprImplByPlanExpr( + builder.GetContext(), + "<=>", + []*plan.Expr{oldExpr, newExpr}, + ) + if err != nil { + return nil, err + } + if unchanged == nil { + unchanged = equal + continue + } + unchanged, err = BindFuncExprImplByPlanExpr( + builder.GetContext(), + "and", + []*plan.Expr{unchanged, equal}, + ) + if err != nil { + return nil, err + } + } + return unchanged, nil +} + func (builder *QueryBuilder) buildUpdateFkUnchangedExpr( tableDef *plan.TableDef, fk *plan.ForeignKeyDef, diff --git a/pkg/sql/plan/deepcopy.go b/pkg/sql/plan/deepcopy.go index a495034195e24..f77394cf6123e 100644 --- a/pkg/sql/plan/deepcopy.go +++ b/pkg/sql/plan/deepcopy.go @@ -106,6 +106,7 @@ func DeepCopyUpdateCtxList(updateCtxList []*plan.UpdateCtx) []*plan.UpdateCtx { SkipInsertOnNullPk: ctx.SkipInsertOnNullPk, InsertPkColIdx: ctx.InsertPkColIdx, CountDeleteAffectRows: ctx.CountDeleteAffectRows, + IgnoreAffectedRows: ctx.IgnoreAffectedRows, } } diff --git a/pkg/sql/plan/update_planner_route_test.go b/pkg/sql/plan/update_planner_route_test.go index e7d5c480006f0..749579b5ce72c 100644 --- a/pkg/sql/plan/update_planner_route_test.go +++ b/pkg/sql/plan/update_planner_route_test.go @@ -317,27 +317,74 @@ func TestBindUpdateForeignKeyRoutingByAffectedColumns(t *testing.T) { require.True(t, hasAssert) }) - t.Run("affected referenced parent key keeps typed legacy route", func(t *testing.T) { + t.Run("affected restricted parent key stays modern with child probe", func(t *testing.T) { mock := NewMockOptimizer(true) prepareEmpDept(mock) - stmt, err := parsers.ParseOne( - mock.CurrentContext().GetContext(), - dialect.MYSQL, - "UPDATE dept SET deptno = 2", - 1, - ) + logicPlan, err := runOneStmt(mock, t, "UPDATE dept SET deptno = 2") require.NoError(t, err) - defer stmt.Free() + query := logicPlan.GetQuery() + require.Equal(t, 1, countUpdateFkPlanNodes(query, planpb.Node_MULTI_UPDATE)) + require.Equal(t, 1, countUpdateFkMarkJoins(query)) + require.Equal(t, 1, countUpdateFkAsserts(query)) + require.True(t, updateFkPlanContainsFunc(query, "<=>")) + }) - builder := NewQueryBuilder(planpb.Query_UPDATE, mock.CurrentContext(), false, true) - _, err = builder.bindUpdate(stmt.(*tree.Update), NewBindContext(builder, nil)) - require.Error(t, err) - route, reason, _ := classifyUpdatePlannerError(err) - require.Equal(t, updatePlannerLegacy, route) - require.Equal(t, updateRouteReasonForeignKey, reason) + t.Run("set default preserves restrict compatibility on modern path", func(t *testing.T) { + mock := NewMockOptimizer(true) + prepareEmpDept(mock) + mock.ctxt.tables["emp"].Fkeys[0].OnUpdate = planpb.ForeignKeyDef_SET_DEFAULT + + logicPlan, err := runOneStmt(mock, t, "UPDATE dept SET deptno = 2") + require.NoError(t, err) + query := logicPlan.GetQuery() + require.Equal(t, 1, countUpdateFkPlanNodes(query, planpb.Node_MULTI_UPDATE)) + require.Equal(t, 1, countUpdateFkMarkJoins(query)) + require.Equal(t, 1, countUpdateFkAsserts(query)) }) + for _, test := range []struct { + name string + action planpb.ForeignKeyDef_RefAction + }{ + {name: "cascade parent key builds child multi update", action: planpb.ForeignKeyDef_CASCADE}, + {name: "set null parent key builds child multi update", action: planpb.ForeignKeyDef_SET_NULL}, + } { + t.Run(test.name, func(t *testing.T) { + mock := NewMockOptimizer(true) + prepareEmpDept(mock) + emp := mock.ctxt.tables["emp"] + emp.Fkeys[0].OnUpdate = test.action + + logicPlan, err := runOneStmt(mock, t, "UPDATE dept SET deptno = 2") + require.NoError(t, err) + query := logicPlan.GetQuery() + require.Equal(t, 2, countUpdateFkPlanNodes(query, planpb.Node_MULTI_UPDATE)) + require.True(t, query.HasForeignKeyAction) + require.True(t, updateFkPlanContainsFunc(query, "<=>")) + hasIndexedChildAction := false + for _, node := range query.Nodes { + if node.NodeType == planpb.Node_MULTI_UPDATE && len(node.UpdateCtxList) > 1 { + hasIndexedChildAction = true + } + if node.NodeType != planpb.Node_MULTI_UPDATE { + continue + } + if len(node.UpdateCtxList) > 0 && node.UpdateCtxList[0].ObjRef.ObjName == "emp" { + for _, updateCtx := range node.UpdateCtxList { + require.True(t, updateCtx.IgnoreAffectedRows) + } + } + if len(node.UpdateCtxList) > 0 && node.UpdateCtxList[0].ObjRef.ObjName == "dept" { + for _, updateCtx := range node.UpdateCtxList { + require.False(t, updateCtx.IgnoreAffectedRows) + } + } + } + require.True(t, hasIndexedChildAction) + }) + } + t.Run("disabled checks skip child probe and parent fallback", func(t *testing.T) { mock := NewMockOptimizer(true) prepareEmpDept(mock) @@ -413,9 +460,22 @@ func TestBindUpdateSelfReferencingForeignKeyRouting(t *testing.T) { require.Equal(t, 1, countUpdateFkAsserts(logicPlan.GetQuery())) }) - t.Run("referenced key keeps typed legacy route", func(t *testing.T) { + t.Run("restricted referenced key stays modern with self probe", func(t *testing.T) { + mock := NewMockOptimizer(true) + prepareSelfRef(mock) + logicPlan, err := runOneStmt(mock, t, "UPDATE self_ref SET id = 2") + require.NoError(t, err) + query := logicPlan.GetQuery() + require.Equal(t, 1, countUpdateFkPlanNodes(query, planpb.Node_MULTI_UPDATE)) + require.Equal(t, 1, countUpdateFkMarkJoins(query)) + require.Equal(t, 1, countUpdateFkAsserts(query)) + require.True(t, updateFkPlanContainsFunc(query, "<=>")) + }) + + t.Run("self cascade is rejected explicitly without legacy fallback", func(t *testing.T) { mock := NewMockOptimizer(true) prepareSelfRef(mock) + mock.ctxt.tables["self_ref"].Fkeys[0].OnUpdate = planpb.ForeignKeyDef_CASCADE stmt, err := parsers.ParseOne( mock.CurrentContext().GetContext(), dialect.MYSQL, @@ -428,9 +488,8 @@ func TestBindUpdateSelfReferencingForeignKeyRouting(t *testing.T) { builder := NewQueryBuilder(planpb.Query_UPDATE, mock.CurrentContext(), false, true) _, err = builder.bindUpdate(stmt.(*tree.Update), NewBindContext(builder, nil)) require.Error(t, err) - route, reason, _ := classifyUpdatePlannerError(err) - require.Equal(t, updatePlannerLegacy, route) - require.Equal(t, updateRouteReasonForeignKey, reason) + route, _, _ := classifyUpdatePlannerError(err) + require.Equal(t, updatePlannerRejected, route) }) } diff --git a/proto/plan.proto b/proto/plan.proto index f7a0f983ec8fd..be8e3b26a760c 100644 --- a/proto/plan.proto +++ b/proto/plan.proto @@ -633,6 +633,7 @@ message UpdateCtx { bool skip_insert_on_null_pk = 10; int32 insert_pk_col_idx = 11; bool count_delete_affect_rows = 12; + bool ignore_affected_rows = 13; } message InsertCtx { diff --git a/test/distributed/cases/foreign_key/update_modern_fk.result b/test/distributed/cases/foreign_key/update_modern_fk.result index 077e66305ff43..94b45a043aa36 100644 --- a/test/distributed/cases/foreign_key/update_modern_fk.result +++ b/test/distributed/cases/foreign_key/update_modern_fk.result @@ -18,15 +18,15 @@ insert into child_single values (30, 1, 'a'); delete from child_single where id = 30; update child_single set parent_id = 3 where id = 10; select * from child_single order by id; -id parent_id note -10 3 b -20 2 NULL +➤ id[4,32,0] ¦ parent_id[4,32,0] ¦ note[12,-1,0] 𝄀 +10 ¦ 3 ¦ b 𝄀 +20 ¦ 2 ¦ null update child_single set parent_id = 99 where id = 10; internal error: Cannot add or update a child row: a foreign key constraint fails select * from child_single order by id; -id parent_id note -10 3 b -20 2 NULL +➤ id[4,32,0] ¦ parent_id[4,32,0] ¦ note[12,-1,0] 𝄀 +10 ¦ 3 ¦ b 𝄀 +20 ¦ 2 ¦ null set foreign_key_checks = 0; update child_single set parent_id = 99 where id = 10; set foreign_key_checks = 1; @@ -35,16 +35,16 @@ update child_single set note = 'orphan' where id = 10; insert into child_single values (30, 1, 'orphan'); Duplicate entry 'orphan' for key 'note' select * from child_single order by id; -id parent_id note -10 99 orphan -20 2 NULL +➤ id[4,32,0] ¦ parent_id[4,32,0] ¦ note[12,-1,0] 𝄀 +10 ¦ 99 ¦ orphan 𝄀 +20 ¦ 2 ¦ null update child_single set parent_id = 1 where id = 10; update child_single set parent_id = case id when 10 then 2 else 98 end; internal error: Cannot add or update a child row: a foreign key constraint fails select * from child_single order by id; -id parent_id note -10 1 orphan -20 2 NULL +➤ id[4,32,0] ¦ parent_id[4,32,0] ¦ note[12,-1,0] 𝄀 +10 ¦ 1 ¦ orphan 𝄀 +20 ¦ 2 ¦ null create table parent_composite ( a int, b int, @@ -62,17 +62,17 @@ update child_composite set a = null, b = 99 where id = 1; update child_composite set a = 99, b = null where id = 2; update child_composite set a = 1, b = 1 where id = 3; select * from child_composite order by id; -id a b -1 NULL 99 -2 99 NULL -3 1 1 +➤ id[4,32,0] ¦ a[4,32,0] ¦ b[4,32,0] 𝄀 +1 ¦ null ¦ 99 𝄀 +2 ¦ 99 ¦ null 𝄀 +3 ¦ 1 ¦ 1 update child_composite set a = 98, b = 98 where id = 3; internal error: Cannot add or update a child row: a foreign key constraint fails select * from child_composite order by id; -id a b -1 NULL 99 -2 99 NULL -3 1 1 +➤ id[4,32,0] ¦ a[4,32,0] ¦ b[4,32,0] 𝄀 +1 ¦ null ¦ 99 𝄀 +2 ¦ 99 ¦ null 𝄀 +3 ¦ 1 ¦ 1 create table self_ref ( id int primary key, parent_id int, @@ -85,9 +85,159 @@ update self_ref set parent_id = 2 where id = 2; update self_ref set parent_id = 99 where id = 2; internal error: Cannot add or update a child row: a foreign key constraint fails select * from self_ref order by id; -id parent_id name -1 1 root -2 2 changed +➤ id[4,32,0] ¦ parent_id[4,32,0] ¦ name[12,-1,0] 𝄀 +1 ¦ 1 ¦ root 𝄀 +2 ¦ 2 ¦ changed +create table parent_restrict ( +id int primary key +); +create table child_restrict ( +id int primary key, +parent_id int, +constraint fk_parent_restrict foreign key (parent_id) +references parent_restrict(id) on update restrict +); +insert into parent_restrict values (1), (2); +insert into child_restrict values (10, 1); +update parent_restrict set id = id where id = 1; +update parent_restrict set id = 11 where id = 1; +internal error: Cannot delete or update a parent row: a foreign key constraint fails +select * from parent_restrict order by id; +➤ id[4,32,0] 𝄀 +1 𝄀 +2 +select * from child_restrict order by id; +➤ id[4,32,0] ¦ parent_id[4,32,0] 𝄀 +10 ¦ 1 +create table parent_cascade ( +id int primary key +); +create table child_cascade ( +id int primary key, +parent_id int, +note varchar(32), +unique key uk_cascade_note(note), +key idx_cascade_parent(parent_id), +constraint fk_parent_cascade foreign key (parent_id) +references parent_cascade(id) on update cascade +); +insert into parent_cascade values (1), (2); +insert into child_cascade values (10, 1, 'a'), (20, 1, 'b'), (30, 2, 'c'); +update parent_cascade set id = 11 where id = 1; +select row_count(); +➤ row_count()[-5,64,0] 𝄀 +1 +select * from parent_cascade order by id; +➤ id[4,32,0] 𝄀 +2 𝄀 +11 +select * from child_cascade order by id; +➤ id[4,32,0] ¦ parent_id[4,32,0] ¦ note[12,-1,0] 𝄀 +10 ¦ 11 ¦ a 𝄀 +20 ¦ 11 ¦ b 𝄀 +30 ¦ 2 ¦ c +update child_cascade set note = 'd' where id = 10; +insert into child_cascade values (40, 11, 'a'); +select * from child_cascade order by id; +➤ id[4,32,0] ¦ parent_id[4,32,0] ¦ note[12,-1,0] 𝄀 +10 ¦ 11 ¦ d 𝄀 +20 ¦ 11 ¦ b 𝄀 +30 ¦ 2 ¦ c 𝄀 +40 ¦ 11 ¦ a +create table parent_set_null ( +id int primary key +); +create table child_set_null ( +id int primary key, +parent_id int, +key idx_set_null_parent(parent_id), +constraint fk_parent_set_null foreign key (parent_id) +references parent_set_null(id) on update set null +); +insert into parent_set_null values (1), (2); +insert into child_set_null values (10, 1), (20, 1), (30, 2); +update parent_set_null set id = 11 where id = 1; +select * from parent_set_null order by id; +➤ id[4,32,0] 𝄀 +2 𝄀 +11 +select * from child_set_null order by id; +➤ id[4,32,0] ¦ parent_id[4,32,0] 𝄀 +10 ¦ null 𝄀 +20 ¦ null 𝄀 +30 ¦ 2 +create table parent_composite_action ( +a int, +b int, +primary key (a, b) +); +create table child_composite_action ( +id int primary key, +a int, +b int, +generated_sum int as (a + b), +key idx_composite_action(a, b), +key idx_generated_sum(generated_sum), +constraint fk_composite_action foreign key (a, b) +references parent_composite_action(a, b) on update cascade +); +insert into parent_composite_action values (1, 2), (3, 4); +insert into child_composite_action(id, a, b) values (10, 1, 2), (20, 3, 4); +update parent_composite_action set a = 11, b = 12 where a = 1 and b = 2; +select row_count(); +➤ row_count()[-5,64,0] 𝄀 +1 +select * from parent_composite_action order by a, b; +➤ a[4,32,0] ¦ b[4,32,0] 𝄀 +3 ¦ 4 𝄀 +11 ¦ 12 +select * from child_composite_action order by id; +➤ id[4,32,0] ¦ a[4,32,0] ¦ b[4,32,0] ¦ generated_sum[4,32,0] 𝄀 +10 ¦ 11 ¦ 12 ¦ 23 𝄀 +20 ¦ 3 ¦ 4 ¦ 7 +create table child_second_cascade ( +id int primary key, +parent_id int, +constraint fk_second_cascade foreign key (parent_id) +references parent_cascade(id) on update cascade +); +insert into child_second_cascade values (1, 11), (2, 2); +update parent_cascade set id = 111 where id = 11; +select row_count(); +➤ row_count()[-5,64,0] 𝄀 +1 +select * from child_cascade order by id; +➤ id[4,32,0] ¦ parent_id[4,32,0] ¦ note[12,-1,0] 𝄀 +10 ¦ 111 ¦ d 𝄀 +20 ¦ 111 ¦ b 𝄀 +30 ¦ 2 ¦ c 𝄀 +40 ¦ 111 ¦ a +select * from child_second_cascade order by id; +➤ id[4,32,0] ¦ parent_id[4,32,0] 𝄀 +1 ¦ 111 𝄀 +2 ¦ 2 +set foreign_key_checks = 0; +update parent_cascade set id = 22 where id = 2; +set foreign_key_checks = 1; +select * from parent_cascade order by id; +➤ id[4,32,0] 𝄀 +22 𝄀 +111 +select * from child_cascade order by id; +➤ id[4,32,0] ¦ parent_id[4,32,0] ¦ note[12,-1,0] 𝄀 +10 ¦ 111 ¦ d 𝄀 +20 ¦ 111 ¦ b 𝄀 +30 ¦ 2 ¦ c 𝄀 +40 ¦ 111 ¦ a +drop table child_second_cascade; +drop table child_composite_action; +drop table parent_composite_action; +drop table child_set_null; +drop table parent_set_null; +drop table child_cascade; +drop table parent_cascade; +drop table child_restrict; +drop table parent_restrict; drop table self_ref; drop table child_composite; drop table parent_composite; diff --git a/test/distributed/cases/foreign_key/update_modern_fk.sql b/test/distributed/cases/foreign_key/update_modern_fk.sql index 83eda70ace224..c49edcff120cb 100644 --- a/test/distributed/cases/foreign_key/update_modern_fk.sql +++ b/test/distributed/cases/foreign_key/update_modern_fk.sql @@ -75,6 +75,118 @@ update self_ref set parent_id = 2 where id = 2; update self_ref set parent_id = 99 where id = 2; select * from self_ref order by id; +create table parent_restrict ( + id int primary key +); + +create table child_restrict ( + id int primary key, + parent_id int, + constraint fk_parent_restrict foreign key (parent_id) + references parent_restrict(id) on update restrict +); + +insert into parent_restrict values (1), (2); +insert into child_restrict values (10, 1); +update parent_restrict set id = id where id = 1; +update parent_restrict set id = 11 where id = 1; +select * from parent_restrict order by id; +select * from child_restrict order by id; + +create table parent_cascade ( + id int primary key +); + +create table child_cascade ( + id int primary key, + parent_id int, + note varchar(32), + unique key uk_cascade_note(note), + key idx_cascade_parent(parent_id), + constraint fk_parent_cascade foreign key (parent_id) + references parent_cascade(id) on update cascade +); + +insert into parent_cascade values (1), (2); +insert into child_cascade values (10, 1, 'a'), (20, 1, 'b'), (30, 2, 'c'); +update parent_cascade set id = 11 where id = 1; +select row_count(); +select * from parent_cascade order by id; +select * from child_cascade order by id; +update child_cascade set note = 'd' where id = 10; +insert into child_cascade values (40, 11, 'a'); +select * from child_cascade order by id; + +create table parent_set_null ( + id int primary key +); + +create table child_set_null ( + id int primary key, + parent_id int, + key idx_set_null_parent(parent_id), + constraint fk_parent_set_null foreign key (parent_id) + references parent_set_null(id) on update set null +); + +insert into parent_set_null values (1), (2); +insert into child_set_null values (10, 1), (20, 1), (30, 2); +update parent_set_null set id = 11 where id = 1; +select * from parent_set_null order by id; +select * from child_set_null order by id; + +create table parent_composite_action ( + a int, + b int, + primary key (a, b) +); + +create table child_composite_action ( + id int primary key, + a int, + b int, + generated_sum int as (a + b), + key idx_composite_action(a, b), + key idx_generated_sum(generated_sum), + constraint fk_composite_action foreign key (a, b) + references parent_composite_action(a, b) on update cascade +); + +insert into parent_composite_action values (1, 2), (3, 4); +insert into child_composite_action(id, a, b) values (10, 1, 2), (20, 3, 4); +update parent_composite_action set a = 11, b = 12 where a = 1 and b = 2; +select row_count(); +select * from parent_composite_action order by a, b; +select * from child_composite_action order by id; + +create table child_second_cascade ( + id int primary key, + parent_id int, + constraint fk_second_cascade foreign key (parent_id) + references parent_cascade(id) on update cascade +); + +insert into child_second_cascade values (1, 11), (2, 2); +update parent_cascade set id = 111 where id = 11; +select row_count(); +select * from child_cascade order by id; +select * from child_second_cascade order by id; + +set foreign_key_checks = 0; +update parent_cascade set id = 22 where id = 2; +set foreign_key_checks = 1; +select * from parent_cascade order by id; +select * from child_cascade order by id; + +drop table child_second_cascade; +drop table child_composite_action; +drop table parent_composite_action; +drop table child_set_null; +drop table parent_set_null; +drop table child_cascade; +drop table parent_cascade; +drop table child_restrict; +drop table parent_restrict; drop table self_ref; drop table child_composite; drop table parent_composite; From c8d2c2fb25f6f704e19de9026851ae53adfc2b85 Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Thu, 30 Jul 2026 17:38:48 +0800 Subject: [PATCH 04/22] fix(plan): validate foreign key update row images --- pkg/sql/plan/bind_insert.go | 2 +- pkg/sql/plan/bind_update.go | 24 +-- pkg/sql/plan/bind_update_fk.go | 146 ++++++++++++++++-- pkg/sql/plan/build.go | 35 +++++ pkg/sql/plan/update_planner_route_test.go | 128 ++++++++++++++- .../cases/foreign_key/fk_self_refer2.result | 2 +- .../cases/foreign_key/update_modern_fk.result | 33 +++- .../cases/foreign_key/update_modern_fk.sql | 28 ++++ 8 files changed, 361 insertions(+), 37 deletions(-) diff --git a/pkg/sql/plan/bind_insert.go b/pkg/sql/plan/bind_insert.go index 01b5fa8f64883..79b9c5570967e 100644 --- a/pkg/sql/plan/bind_insert.go +++ b/pkg/sql/plan/bind_insert.go @@ -1059,7 +1059,7 @@ func (builder *QueryBuilder) appendModernChildFkMarkOks( selectTag int32, childColPos func(colName string) int32, ) (int32, []*plan.Expr, error) { - selectNode := builder.qry.Nodes[lastNodeID] + selectNode := builder.updateInputProjectNode(lastNodeID) id2name := make(map[uint64]string, len(tableDef.Cols)) for _, col := range tableDef.Cols { diff --git a/pkg/sql/plan/bind_update.go b/pkg/sql/plan/bind_update.go index 5da98b6c309e4..c1f67b47585b6 100644 --- a/pkg/sql/plan/bind_update.go +++ b/pkg/sql/plan/bind_update.go @@ -421,18 +421,6 @@ func (builder *QueryBuilder) bindUpdate(stmt *tree.Update, bindCtx *BindContext) } } - lastNodeID, selectNodeTag, selectNode, err = builder.appendUpdateForeignKeyChecks( - bindCtx, - dmlCtx, - lastNodeID, - selectNodeTag, - oldColName2Idx, - newColName2Idx, - ) - if err != nil { - return 0, err - } - for i, tableDef := range dmlCtx.tableDefs { if updateAutoIncrCols[i] { lastNodeID = builder.appendNode(&plan.Node{ @@ -449,6 +437,18 @@ func (builder *QueryBuilder) bindUpdate(stmt *tree.Update, bindCtx *BindContext) } } + lastNodeID, selectNodeTag, selectNode, err = builder.appendUpdateForeignKeyChecks( + bindCtx, + dmlCtx, + lastNodeID, + selectNodeTag, + oldColName2Idx, + newColName2Idx, + ) + if err != nil { + return 0, err + } + idxScanNodes := make([][]*plan.Node, len(dmlCtx.tableDefs)) pkNeedUpdate := make([]bool, len(dmlCtx.tableDefs)) idxNeedUpdate := make([][]bool, len(dmlCtx.tableDefs)) diff --git a/pkg/sql/plan/bind_update_fk.go b/pkg/sql/plan/bind_update_fk.go index 40c9a55717977..c6d852625a6ff 100644 --- a/pkg/sql/plan/bind_update_fk.go +++ b/pkg/sql/plan/bind_update_fk.go @@ -23,6 +23,14 @@ import ( "github.com/matrixorigin/matrixone/pkg/pb/plan" ) +func (builder *QueryBuilder) updateInputProjectNode(nodeID int32) *plan.Node { + node := builder.qry.Nodes[nodeID] + if node.NodeType == plan.Node_PRE_INSERT && len(node.Children) == 1 { + return builder.qry.Nodes[node.Children[0]] + } + return node +} + func (builder *QueryBuilder) appendUpdateForeignKeyChecks( bindCtx *BindContext, dmlCtx *DMLContext, @@ -62,16 +70,23 @@ func (builder *QueryBuilder) appendUpdateForeignKeyChecks( continue } + validatedFks := make([]*plan.ForeignKeyDef, 0, len(affectedFks)) + for _, fk := range affectedFks { + if fk.ForeignTbl != 0 { + validatedFks = append(validatedFks, fk) + } + } + if len(validatedFks) == 0 { + continue + } + fkTableDef := *tableDef - fkTableDef.Fkeys = make([]*plan.ForeignKeyDef, len(affectedFks)) - for j, fk := range affectedFks { + fkTableDef.Fkeys = make([]*plan.ForeignKeyDef, len(validatedFks)) + for j, fk := range validatedFks { fkTableDef.Fkeys[j] = DeepCopyFkey(fk) - if fkTableDef.Fkeys[j].ForeignTbl == 0 { - fkTableDef.Fkeys[j].ForeignTbl = tableDef.TblId - } } - sourceNode := builder.qry.Nodes[lastNodeID] + sourceNode := builder.updateInputProjectNode(lastNodeID) projLen := len(sourceNode.ProjectList) projectTypes := make([]plan.Type, projLen) for j, expr := range sourceNode.ProjectList { @@ -100,7 +115,7 @@ func (builder *QueryBuilder) appendUpdateForeignKeyChecks( errExpr := makePlan2StringConstExprWithType( "Cannot add or update a child row: a foreign key constraint fails", ) - for j, fk := range affectedFks { + for j, fk := range validatedFks { unchanged, buildErr := builder.buildUpdateFkUnchangedExpr( tableDef, fk, @@ -265,6 +280,21 @@ func (builder *QueryBuilder) appendUpdateParentForeignKeyChecks( }) for _, affectedFK := range affected { + if affectedFK.childTableDef.TblId == tableDef.TblId { + switch affectedFK.fk.OnUpdate { + case plan.ForeignKeyDef_RESTRICT, + plan.ForeignKeyDef_NO_ACTION, + plan.ForeignKeyDef_SET_DEFAULT: + case plan.ForeignKeyDef_CASCADE, plan.ForeignKeyDef_SET_NULL: + return 0, 0, newLegacyUpdatePlannerRouteError( + updateRouteReasonForeignKey, + moerr.NewUnsupportedDML( + builder.GetContext(), + "self-referencing parent foreign key action", + ), + ) + } + } switch affectedFK.fk.OnUpdate { case plan.ForeignKeyDef_RESTRICT, plan.ForeignKeyDef_NO_ACTION, @@ -307,9 +337,12 @@ func (builder *QueryBuilder) appendUpdateParentForeignKeyChecks( mutationByChild := make(map[uint64]struct{}, len(mutations)) for _, mutation := range mutations { if _, exists := mutationByChild[mutation.childTableDef.TblId]; exists { - return 0, 0, moerr.NewNotSupported( - builder.GetContext(), - "multiple parent foreign key actions targeting the same child table", + return 0, 0, newLegacyUpdatePlannerRouteError( + updateRouteReasonForeignKey, + moerr.NewUnsupportedDML( + builder.GetContext(), + "multiple parent foreign key actions targeting the same child table", + ), ) } mutationByChild[mutation.childTableDef.TblId] = struct{}{} @@ -345,8 +378,26 @@ func (builder *QueryBuilder) validateModernUpdateParentMutation( ) error { childTableDef := affectedFK.childTableDef ensureName2ColIndexForReplace(childTableDef) + parentColByID := make(map[uint64]*plan.ColDef, len(parentTableDef.Cols)) + for _, col := range parentTableDef.Cols { + parentColByID[col.ColId] = col + } + for _, parentColID := range affectedFK.fk.ForeignCols { + if col := parentColByID[parentColID]; col != nil && col.Typ.AutoIncr { + return newLegacyUpdatePlannerRouteError( + updateRouteReasonForeignKey, + moerr.NewUnsupportedDML( + builder.GetContext(), + "parent foreign key action changing auto-increment referenced key", + ), + ) + } + } if childTableDef.TblId == parentTableDef.TblId { - return moerr.NewNotSupported(builder.GetContext(), "self-referencing parent foreign key action") + return newLegacyUpdatePlannerRouteError( + updateRouteReasonForeignKey, + moerr.NewUnsupportedDML(builder.GetContext(), "self-referencing parent foreign key action"), + ) } _, hasIrregularIndex := getValidIndexes(childTableDef) if hasIrregularIndex { @@ -373,9 +424,12 @@ func (builder *QueryBuilder) validateModernUpdateParentMutation( } for _, childColID := range affectedFK.fk.Cols { if _, ok := primaryNames[childColIDToName[childColID]]; ok { - return moerr.NewNotSupported( - builder.GetContext(), - "parent foreign key action changing child primary key", + return newLegacyUpdatePlannerRouteError( + updateRouteReasonForeignKey, + moerr.NewUnsupportedDML( + builder.GetContext(), + "parent foreign key action changing child primary key", + ), ) } } @@ -384,6 +438,24 @@ func (builder *QueryBuilder) validateModernUpdateParentMutation( for _, childColID := range affectedFK.fk.Cols { updatedChildCols[childColID] = struct{}{} } + for _, col := range childTableDef.Cols { + if col.GeneratedCol != nil { + updatedChildCols[col.ColId] = struct{}{} + } + } + for _, outgoingFK := range childTableDef.Fkeys { + if outgoingFK == affectedFK.fk { + continue + } + for _, childColID := range outgoingFK.Cols { + if _, changed := updatedChildCols[childColID]; changed { + return moerr.NewNotSupported( + builder.GetContext(), + "parent foreign key action changing child column constrained by another foreign key", + ) + } + } + } visited := make(map[uint64]struct{}, len(childTableDef.RefChildTbls)) for _, grandchildID := range childTableDef.RefChildTbls { if grandchildID == 0 { @@ -854,7 +926,7 @@ func (builder *QueryBuilder) appendUpdateParentRestrictCheck( oldColName2Idx map[string]int32, newColName2Idx map[string]int32, ) (int32, int32, error) { - sourceNode := builder.qry.Nodes[lastNodeID] + sourceNode := builder.updateInputProjectNode(lastNodeID) sourceTypes := make([]plan.Type, len(sourceNode.ProjectList)) for i, expr := range sourceNode.ProjectList { sourceTypes[i] = expr.Typ @@ -952,6 +1024,50 @@ func (builder *QueryBuilder) appendUpdateParentRestrictCheck( return 0, 0, err } } + if affectedFK.childTableDef.TblId == parentTableDef.TblId { + var newKeyHasNull *plan.Expr + for _, parentColID := range affectedFK.fk.ForeignCols { + parentColName := parentColIDToName[parentColID] + parentPos := newColName2Idx[parentAlias+"."+parentColName] + newKeyExpr := &plan.Expr{ + Typ: sourceNode.ProjectList[parentPos].Typ, + Expr: &plan.Expr_Col{Col: &plan.ColRef{ + RelPos: selectNodeTag, + ColPos: parentPos, + }}, + } + isNull, buildErr := BindFuncExprImplByPlanExpr( + builder.GetContext(), + "isnull", + []*plan.Expr{newKeyExpr}, + ) + if buildErr != nil { + return 0, 0, buildErr + } + if newKeyHasNull == nil { + newKeyHasNull = isNull + continue + } + newKeyHasNull, buildErr = BindFuncExprImplByPlanExpr( + builder.GetContext(), + "or", + []*plan.Expr{newKeyHasNull, isNull}, + ) + if buildErr != nil { + return 0, 0, buildErr + } + } + if newKeyHasNull != nil { + ok, err = BindFuncExprImplByPlanExpr( + builder.GetContext(), + "or", + []*plan.Expr{ok, newKeyHasNull}, + ) + if err != nil { + return 0, 0, err + } + } + } assertExpr, err := BindFuncExprImplByPlanExpr( builder.GetContext(), "assert", diff --git a/pkg/sql/plan/build.go b/pkg/sql/plan/build.go index 90957523e35ce..5ab7357d90358 100644 --- a/pkg/sql/plan/build.go +++ b/pkg/sql/plan/build.go @@ -335,6 +335,41 @@ func bindAndOptimizeUpdateQuery(ctx CompilerContext, stmt *tree.Update, isPrepar if err != nil { return nil, err } + enabled, err := IsForeignKeyChecksEnabled(ctx) + if err != nil { + return nil, err + } + if enabled { + tblInfo, resolveErr := getUpdateTableInfo(ctx, stmt) + if resolveErr != nil { + return nil, resolveErr + } + for i, tableDef := range tblInfo.tableDefs { + if len(tblInfo.updateKeys[i]) == 0 { + continue + } + selfFkeys := make([]*plan.ForeignKeyDef, 0, len(tableDef.Fkeys)) + for _, fk := range tableDef.Fkeys { + if fk.ForeignTbl == 0 { + selfFkeys = append(selfFkeys, fk) + } + } + if len(selfFkeys) == 0 { + continue + } + sqls, genErr := genSqlsForCheckFKSelfRefer( + ctx.GetContext(), + tblInfo.objRef[i].SchemaName, + tableDef.Name, + tableDef.Cols, + selfFkeys, + ) + if genErr != nil { + return nil, genErr + } + query.DetectSqls = append(query.DetectSqls, sqls...) + } + } recordUpdatePlannerRoute(updatePlannerModern, updateRouteReasonNone, "selected") return &Plan{ Plan: &plan.Plan_Query{ diff --git a/pkg/sql/plan/update_planner_route_test.go b/pkg/sql/plan/update_planner_route_test.go index 749579b5ce72c..6b07f3687ef56 100644 --- a/pkg/sql/plan/update_planner_route_test.go +++ b/pkg/sql/plan/update_planner_route_test.go @@ -432,6 +432,35 @@ func TestBindUpdateForeignKeyRoutingByAffectedColumns(t *testing.T) { require.Equal(t, 1, countUpdateFkAsserts(logicPlan.GetQuery())) require.True(t, updateFkPlanContainsFunc(logicPlan.GetQuery(), "<=>")) }) + + t.Run("parent cascade changing another child foreign key is rejected", func(t *testing.T) { + mock := NewMockOptimizer(true) + prepareEmpDept(mock) + emp := mock.ctxt.tables["emp"] + emp.Fkeys[0].OnUpdate = planpb.ForeignKeyDef_CASCADE + otherParent := mock.ctxt.tables["nation"] + emp.Fkeys = append(emp.Fkeys, &planpb.ForeignKeyDef{ + Name: "fk_emp_other_parent", + Cols: []uint64{7}, + ForeignTbl: otherParent.TblId, + ForeignCols: []uint64{0}, + }) + + stmt, err := parsers.ParseOne( + mock.CurrentContext().GetContext(), + dialect.MYSQL, + "UPDATE dept SET deptno = 2", + 1, + ) + require.NoError(t, err) + defer stmt.Free() + + builder := NewQueryBuilder(planpb.Query_UPDATE, mock.CurrentContext(), false, true) + _, err = builder.bindUpdate(stmt.(*tree.Update), NewBindContext(builder, nil)) + require.ErrorContains(t, err, "child column constrained by another foreign key") + route, _, _ := classifyUpdatePlannerError(err) + require.Equal(t, updatePlannerRejected, route) + }) } func TestBindUpdateSelfReferencingForeignKeyRouting(t *testing.T) { @@ -456,11 +485,12 @@ func TestBindUpdateSelfReferencingForeignKeyRouting(t *testing.T) { logicPlan, err := runOneStmt(mock, t, "UPDATE self_ref SET parent_id = 2") require.NoError(t, err) require.Equal(t, 1, countUpdateFkPlanNodes(logicPlan.GetQuery(), planpb.Node_MULTI_UPDATE)) - require.Equal(t, 1, countUpdateFkMarkJoins(logicPlan.GetQuery())) - require.Equal(t, 1, countUpdateFkAsserts(logicPlan.GetQuery())) + require.Equal(t, 0, countUpdateFkMarkJoins(logicPlan.GetQuery())) + require.Equal(t, 0, countUpdateFkAsserts(logicPlan.GetQuery())) + require.NotEmpty(t, logicPlan.GetQuery().DetectSqls) }) - t.Run("restricted referenced key stays modern with self probe", func(t *testing.T) { + t.Run("restricted referenced key uses final self validation", func(t *testing.T) { mock := NewMockOptimizer(true) prepareSelfRef(mock) logicPlan, err := runOneStmt(mock, t, "UPDATE self_ref SET id = 2") @@ -469,10 +499,11 @@ func TestBindUpdateSelfReferencingForeignKeyRouting(t *testing.T) { require.Equal(t, 1, countUpdateFkPlanNodes(query, planpb.Node_MULTI_UPDATE)) require.Equal(t, 1, countUpdateFkMarkJoins(query)) require.Equal(t, 1, countUpdateFkAsserts(query)) - require.True(t, updateFkPlanContainsFunc(query, "<=>")) + require.NotEmpty(t, query.DetectSqls) + require.True(t, updateFkPlanContainsFunc(query, "isnull")) }) - t.Run("self cascade is rejected explicitly without legacy fallback", func(t *testing.T) { + t.Run("self cascade uses typed legacy validation", func(t *testing.T) { mock := NewMockOptimizer(true) prepareSelfRef(mock) mock.ctxt.tables["self_ref"].Fkeys[0].OnUpdate = planpb.ForeignKeyDef_CASCADE @@ -488,8 +519,91 @@ func TestBindUpdateSelfReferencingForeignKeyRouting(t *testing.T) { builder := NewQueryBuilder(planpb.Query_UPDATE, mock.CurrentContext(), false, true) _, err = builder.bindUpdate(stmt.(*tree.Update), NewBindContext(builder, nil)) require.Error(t, err) - route, _, _ := classifyUpdatePlannerError(err) - require.Equal(t, updatePlannerRejected, route) + route, reason, _ := classifyUpdatePlannerError(err) + require.Equal(t, updatePlannerLegacy, route) + require.Equal(t, updateRouteReasonForeignKey, reason) + }) + + t.Run("disabled checks omit self detect sql", func(t *testing.T) { + mock := NewMockOptimizer(true) + prepareSelfRef(mock) + mock.ctxt.SetContext(context.WithValue( + mock.ctxt.GetContext(), + defines.DisableFkCheck{}, + true, + )) + logicPlan, err := runOneStmt(mock, t, "UPDATE self_ref SET parent_id = 99") + require.NoError(t, err) + require.Empty(t, logicPlan.GetQuery().DetectSqls) + require.Equal(t, 0, countUpdateFkMarkJoins(logicPlan.GetQuery())) + require.Equal(t, 0, countUpdateFkAsserts(logicPlan.GetQuery())) + }) +} + +func TestBindUpdateAutoIncrementRunsBeforeForeignKeys(t *testing.T) { + prepareEmpDept := func(mock *MockOptimizer) { + emp := mock.ctxt.tables["emp"] + dept := mock.ctxt.tables["dept"] + emp.TblId = 88887 + emp.Fkeys[0].ForeignTbl = dept.TblId + emp.Fkeys[0].ForeignCols = []uint64{0} + dept.RefChildTbls = []uint64{emp.TblId} + mock.ctxt.id2name[emp.TblId] = "emp" + } + bindDirect := func(t *testing.T, mock *MockOptimizer, sql string) *planpb.Query { + stmt, err := parsers.ParseOne(mock.CurrentContext().GetContext(), dialect.MYSQL, sql, 1) + require.NoError(t, err) + defer stmt.Free() + builder := NewQueryBuilder(planpb.Query_UPDATE, mock.CurrentContext(), false, true) + _, err = builder.bindUpdate(stmt.(*tree.Update), NewBindContext(builder, nil)) + require.NoError(t, err) + return builder.qry + } + firstNode := func(query *planpb.Query, match func(*planpb.Node) bool) int { + for i, node := range query.Nodes { + if match(node) { + return i + } + } + return -1 + } + + t.Run("child check sees generated key", func(t *testing.T) { + mock := NewMockOptimizer(true) + prepareEmpDept(mock) + mock.ctxt.tables["emp"].Cols[7].Typ.AutoIncr = true + query := bindDirect(t, mock, "UPDATE emp SET deptno = DEFAULT") + preInsert := firstNode(query, func(node *planpb.Node) bool { + return node.NodeType == planpb.Node_PRE_INSERT + }) + markJoin := firstNode(query, func(node *planpb.Node) bool { + return node.NodeType == planpb.Node_JOIN && node.JoinType == planpb.Node_MARK + }) + require.NotEqual(t, -1, preInsert) + require.NotEqual(t, -1, markJoin) + require.Less(t, preInsert, markJoin) + }) + + t.Run("parent action on generated key uses legacy planner", func(t *testing.T) { + mock := NewMockOptimizer(true) + prepareEmpDept(mock) + mock.ctxt.tables["dept"].Cols[0].Typ.AutoIncr = true + mock.ctxt.tables["emp"].Fkeys[0].OnUpdate = planpb.ForeignKeyDef_CASCADE + stmt, err := parsers.ParseOne( + mock.CurrentContext().GetContext(), + dialect.MYSQL, + "UPDATE dept SET deptno = DEFAULT", + 1, + ) + require.NoError(t, err) + defer stmt.Free() + + builder := NewQueryBuilder(planpb.Query_UPDATE, mock.CurrentContext(), false, true) + _, err = builder.bindUpdate(stmt.(*tree.Update), NewBindContext(builder, nil)) + require.ErrorContains(t, err, "auto-increment referenced key") + route, reason, _ := classifyUpdatePlannerError(err) + require.Equal(t, updatePlannerLegacy, route) + require.Equal(t, updateRouteReasonForeignKey, reason) }) } diff --git a/test/distributed/cases/foreign_key/fk_self_refer2.result b/test/distributed/cases/foreign_key/fk_self_refer2.result index 7639398e740e4..00347e527c3dc 100644 --- a/test/distributed/cases/foreign_key/fk_self_refer2.result +++ b/test/distributed/cases/foreign_key/fk_self_refer2.result @@ -79,7 +79,7 @@ delete from t1 where a = 4; delete from t1 where a = 6; update t1 set b = 8 where a = 3; update t1 set b = 6 where a = 3; -internal error: Cannot add or update a child row: a foreign key constraint fails +Cannot add or update a child row: a foreign key constraint fails delete from t1 where a = 8; internal error: Cannot delete or update a parent row: a foreign key constraint fails delete from t1 where a = 3; diff --git a/test/distributed/cases/foreign_key/update_modern_fk.result b/test/distributed/cases/foreign_key/update_modern_fk.result index 94b45a043aa36..ea9ff26699cab 100644 --- a/test/distributed/cases/foreign_key/update_modern_fk.result +++ b/test/distributed/cases/foreign_key/update_modern_fk.result @@ -82,8 +82,12 @@ constraint fk_self foreign key (parent_id) references self_ref(id) insert into self_ref values (1, 1, 'root'), (2, 1, 'child'); update self_ref set name = 'changed' where id = 2; update self_ref set parent_id = 2 where id = 2; +set foreign_key_checks = 0; update self_ref set parent_id = 99 where id = 2; -internal error: Cannot add or update a child row: a foreign key constraint fails +update self_ref set parent_id = 2 where id = 2; +set foreign_key_checks = 1; +update self_ref set parent_id = 99 where id = 2; +Cannot add or update a child row: a foreign key constraint fails select * from self_ref order by id; ➤ id[4,32,0] ¦ parent_id[4,32,0] ¦ name[12,-1,0] 𝄀 1 ¦ 1 ¦ root 𝄀 @@ -229,6 +233,33 @@ select * from child_cascade order by id; 20 ¦ 111 ¦ b 𝄀 30 ¦ 2 ¦ c 𝄀 40 ¦ 111 ¦ a +create table parent_dual_a ( +id int primary key +); +create table parent_dual_b ( +id int primary key +); +create table child_dual_fk ( +x int, +constraint fk_dual_a foreign key (x) +references parent_dual_a(id) on update cascade, +constraint fk_dual_b foreign key (x) +references parent_dual_b(id) +); +insert into parent_dual_a values (1); +insert into parent_dual_b values (1); +insert into child_dual_fk values (1); +update parent_dual_a set id = 2 where id = 1; +not supported: parent foreign key action changing child column constrained by another foreign key +select * from parent_dual_a order by id; +➤ id[4,32,0] 𝄀 +1 +select * from child_dual_fk; +➤ x[4,32,0] 𝄀 +1 +drop table child_dual_fk; +drop table parent_dual_b; +drop table parent_dual_a; drop table child_second_cascade; drop table child_composite_action; drop table parent_composite_action; diff --git a/test/distributed/cases/foreign_key/update_modern_fk.sql b/test/distributed/cases/foreign_key/update_modern_fk.sql index c49edcff120cb..83de99f04609c 100644 --- a/test/distributed/cases/foreign_key/update_modern_fk.sql +++ b/test/distributed/cases/foreign_key/update_modern_fk.sql @@ -72,6 +72,10 @@ create table self_ref ( insert into self_ref values (1, 1, 'root'), (2, 1, 'child'); update self_ref set name = 'changed' where id = 2; update self_ref set parent_id = 2 where id = 2; +set foreign_key_checks = 0; +update self_ref set parent_id = 99 where id = 2; +update self_ref set parent_id = 2 where id = 2; +set foreign_key_checks = 1; update self_ref set parent_id = 99 where id = 2; select * from self_ref order by id; @@ -178,6 +182,30 @@ set foreign_key_checks = 1; select * from parent_cascade order by id; select * from child_cascade order by id; +create table parent_dual_a ( + id int primary key +); +create table parent_dual_b ( + id int primary key +); +create table child_dual_fk ( + x int, + constraint fk_dual_a foreign key (x) + references parent_dual_a(id) on update cascade, + constraint fk_dual_b foreign key (x) + references parent_dual_b(id) +); + +insert into parent_dual_a values (1); +insert into parent_dual_b values (1); +insert into child_dual_fk values (1); +update parent_dual_a set id = 2 where id = 1; +select * from parent_dual_a order by id; +select * from child_dual_fk; + +drop table child_dual_fk; +drop table parent_dual_b; +drop table parent_dual_a; drop table child_second_cascade; drop table child_composite_action; drop table parent_composite_action; From ed6a84bf09474e42662c880dbdc6e00b0c6a0b5d Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Thu, 30 Jul 2026 23:29:19 +0800 Subject: [PATCH 05/22] fix(plan): preserve update schema after preinsert --- pkg/sql/plan/bind_update.go | 1 + pkg/sql/plan/bind_update_fk.go | 8 +++++--- pkg/sql/plan/update_planner_route_test.go | 17 +++++++++++++++++ .../cases/foreign_key/fk_self_refer.result | 2 +- .../cases/foreign_key/fk_self_refer3.result | 4 ++-- .../cases/foreign_key/update_modern_fk.result | 14 ++++++++++++++ .../cases/foreign_key/update_modern_fk.sql | 14 ++++++++++++++ 7 files changed, 54 insertions(+), 6 deletions(-) diff --git a/pkg/sql/plan/bind_update.go b/pkg/sql/plan/bind_update.go index 7cc9dbf1a157d..4b32b1ffcbd1a 100644 --- a/pkg/sql/plan/bind_update.go +++ b/pkg/sql/plan/bind_update.go @@ -445,6 +445,7 @@ func (builder *QueryBuilder) bindUpdate(stmt *tree.Update, bindCtx *BindContext) dmlCtx, lastNodeID, selectNodeTag, + selectNode, oldColName2Idx, newColName2Idx, ) diff --git a/pkg/sql/plan/bind_update_fk.go b/pkg/sql/plan/bind_update_fk.go index c6d852625a6ff..149faa5eda1b9 100644 --- a/pkg/sql/plan/bind_update_fk.go +++ b/pkg/sql/plan/bind_update_fk.go @@ -36,6 +36,7 @@ func (builder *QueryBuilder) appendUpdateForeignKeyChecks( dmlCtx *DMLContext, lastNodeID int32, selectNodeTag int32, + selectNode *plan.Node, oldColName2Idx map[string]int32, newColName2Idx map[string]int32, ) (int32, int32, *plan.Node, error) { @@ -44,7 +45,7 @@ func (builder *QueryBuilder) appendUpdateForeignKeyChecks( return 0, 0, nil, err } if !enabled { - return lastNodeID, selectNodeTag, builder.qry.Nodes[lastNodeID], nil + return lastNodeID, selectNodeTag, selectNode, nil } for i, tableDef := range dmlCtx.tableDefs { @@ -86,7 +87,7 @@ func (builder *QueryBuilder) appendUpdateForeignKeyChecks( fkTableDef.Fkeys[j] = DeepCopyFkey(fk) } - sourceNode := builder.updateInputProjectNode(lastNodeID) + sourceNode := selectNode projLen := len(sourceNode.ProjectList) projectTypes := make([]plan.Type, projLen) for j, expr := range sourceNode.ProjectList { @@ -176,9 +177,10 @@ func (builder *QueryBuilder) appendUpdateForeignKeyChecks( }, bindCtx) selectNodeTag = validatedTag + selectNode = builder.qry.Nodes[lastNodeID] } - return lastNodeID, selectNodeTag, builder.qry.Nodes[lastNodeID], nil + return lastNodeID, selectNodeTag, selectNode, nil } func affectedUpdateChildFks( diff --git a/pkg/sql/plan/update_planner_route_test.go b/pkg/sql/plan/update_planner_route_test.go index 6b07f3687ef56..ca1b5d98a0adf 100644 --- a/pkg/sql/plan/update_planner_route_test.go +++ b/pkg/sql/plan/update_planner_route_test.go @@ -605,6 +605,23 @@ func TestBindUpdateAutoIncrementRunsBeforeForeignKeys(t *testing.T) { require.Equal(t, updatePlannerLegacy, route) require.Equal(t, updateRouteReasonForeignKey, reason) }) + + t.Run("disabled checks preserve pre-insert input schema", func(t *testing.T) { + mock := NewMockOptimizer(true) + prepareEmpDept(mock) + mock.ctxt.tables["emp"].Cols[0].Typ.AutoIncr = true + mock.ctxt.SetContext(context.WithValue( + mock.ctxt.GetContext(), + defines.DisableFkCheck{}, + true, + )) + + query := bindDirect(t, mock, "UPDATE emp SET empno = DEFAULT") + require.NotNil(t, query) + require.NotEqual(t, -1, firstNode(query, func(node *planpb.Node) bool { + return node.NodeType == planpb.Node_PRE_INSERT + })) + }) } func TestResolveSingleTablePreservesForeignKeyPolicy(t *testing.T) { diff --git a/test/distributed/cases/foreign_key/fk_self_refer.result b/test/distributed/cases/foreign_key/fk_self_refer.result index f4a74bdd8f2ea..2302d189aa22c 100644 --- a/test/distributed/cases/foreign_key/fk_self_refer.result +++ b/test/distributed/cases/foreign_key/fk_self_refer.result @@ -39,7 +39,7 @@ internal error: Cannot delete or update a parent row: a foreign key constraint f update t1 set b=NULL where a= 4; constraint violation: Column 'b' cannot be null update t1 set b=5 where a= 4; -internal error: Cannot add or update a child row: a foreign key constraint fails +Cannot add or update a child row: a foreign key constraint fails insert into t1 values (3,4); update t1 set b = 3 where a= 4; select * from t1; diff --git a/test/distributed/cases/foreign_key/fk_self_refer3.result b/test/distributed/cases/foreign_key/fk_self_refer3.result index c06ba0f73d32f..7369f60f7b940 100644 --- a/test/distributed/cases/foreign_key/fk_self_refer3.result +++ b/test/distributed/cases/foreign_key/fk_self_refer3.result @@ -24,13 +24,13 @@ insert into t1 values (1,4,3,1,4,1,3); update t1 set b = 5 where b = 4; internal error: Cannot delete or update a parent row: a foreign key constraint fails update t1 set e = 5 where b = 4; -internal error: Cannot add or update a child row: a foreign key constraint fails +Cannot add or update a child row: a foreign key constraint fails update t1 set e = NULL where b = 4; update t1 set b = 5 where b = 4; update t1 set c = 4 where b = 5; internal error: Cannot delete or update a parent row: a foreign key constraint fails update t1 set g = 4 where b = 5; -internal error: Cannot add or update a child row: a foreign key constraint fails +Cannot add or update a child row: a foreign key constraint fails update t1 set g = 2 where b = 5; update t1 set c = 4 where b = 5; delete from t1 where b = 5; diff --git a/test/distributed/cases/foreign_key/update_modern_fk.result b/test/distributed/cases/foreign_key/update_modern_fk.result index ea9ff26699cab..9e75bf82dfdd7 100644 --- a/test/distributed/cases/foreign_key/update_modern_fk.result +++ b/test/distributed/cases/foreign_key/update_modern_fk.result @@ -257,6 +257,20 @@ select * from parent_dual_a order by id; select * from child_dual_fk; ➤ x[4,32,0] 𝄀 1 +set foreign_key_checks = 0; +create table update_preinsert_index ( +a int not null auto_increment primary key, +b varchar(25) not null, +c datetime, +key idx_b(b), +key idx_c(c) +); +insert into update_preinsert_index(b, c) +values ('a', '2020-09-08'), ('b', '2020-09-09'); +update update_preinsert_index set a = 90; +Duplicate entry '90' for key 'a' +set foreign_key_checks = 1; +drop table update_preinsert_index; drop table child_dual_fk; drop table parent_dual_b; drop table parent_dual_a; diff --git a/test/distributed/cases/foreign_key/update_modern_fk.sql b/test/distributed/cases/foreign_key/update_modern_fk.sql index 83de99f04609c..62ec3112e1e19 100644 --- a/test/distributed/cases/foreign_key/update_modern_fk.sql +++ b/test/distributed/cases/foreign_key/update_modern_fk.sql @@ -203,6 +203,20 @@ update parent_dual_a set id = 2 where id = 1; select * from parent_dual_a order by id; select * from child_dual_fk; +set foreign_key_checks = 0; +create table update_preinsert_index ( + a int not null auto_increment primary key, + b varchar(25) not null, + c datetime, + key idx_b(b), + key idx_c(c) +); +insert into update_preinsert_index(b, c) +values ('a', '2020-09-08'), ('b', '2020-09-09'); +update update_preinsert_index set a = 90; +set foreign_key_checks = 1; +drop table update_preinsert_index; + drop table child_dual_fk; drop table parent_dual_b; drop table parent_dual_a; From afecfe0e7214dae336092376ba816a5e0312b944 Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Fri, 31 Jul 2026 11:27:51 +0800 Subject: [PATCH 06/22] fix(plan): keep FK updates cache sensitive --- pkg/sql/plan/bind_update.go | 11 ++++++++++ pkg/sql/plan/build_test.go | 22 +++++++++++++++++-- .../prepare/prepare_fk_action_cache.result | 18 +++++++++++++++ .../cases/prepare/prepare_fk_action_cache.sql | 16 ++++++++++++++ 4 files changed, 65 insertions(+), 2 deletions(-) diff --git a/pkg/sql/plan/bind_update.go b/pkg/sql/plan/bind_update.go index 4b32b1ffcbd1a..6e2bbd8b27126 100644 --- a/pkg/sql/plan/bind_update.go +++ b/pkg/sql/plan/bind_update.go @@ -78,6 +78,17 @@ func (builder *QueryBuilder) bindUpdate(stmt *tree.Update, bindCtx *BindContext) if err != nil { return 0, err } + for i, tableDef := range dmlCtx.tableDefs { + if len(dmlCtx.updateCol2Expr[i]) > 0 && + (len(tableDef.Fkeys) > 0 || len(tableDef.RefChildTbls) > 0) { + // FK validation and parent-side actions depend on the current + // foreign_key_checks value. Mark the plan before that value is + // inspected so a plan built with checks disabled cannot be reused + // after they are enabled (or vice versa). + builder.qry.HasForeignKeyAction = true + break + } + } if err = validateUpdateTargetSubqueries(builder.compCtx, stmt, dmlCtx.objRefs, dmlCtx.tableDefs); err != nil { return 0, err } diff --git a/pkg/sql/plan/build_test.go b/pkg/sql/plan/build_test.go index c8e06e8006884..3bf7d9a9c3d4c 100644 --- a/pkg/sql/plan/build_test.go +++ b/pkg/sql/plan/build_test.go @@ -2523,12 +2523,12 @@ func TestUpdateFallbackGeneratedColumnChainUsesFreshExpr(t *testing.T) { } func TestPreparedForeignKeyActionsMarkQueryUncacheable(t *testing.T) { - t.Run("ordinary child update keeps prepare cacheable", func(t *testing.T) { + t.Run("ordinary child update marks prepare uncacheable", func(t *testing.T) { mock := NewMockOptimizer(true) setMockEmpDeptForeignKeyAction(t, mock, plan.ForeignKeyDef_SET_NULL, plan.ForeignKeyDef_CASCADE) query := buildPreparedQuery(t, mock, "prepare stmt1 from update emp set deptno = ? where empno = ?") - require.False(t, query.GetHasForeignKeyAction()) + require.True(t, query.GetHasForeignKeyAction()) }) t.Run("parent update cascade marks prepare uncacheable", func(t *testing.T) { @@ -2539,6 +2539,24 @@ func TestPreparedForeignKeyActionsMarkQueryUncacheable(t *testing.T) { require.True(t, query.GetHasForeignKeyAction()) }) + t.Run("child update remains uncacheable with checks disabled", func(t *testing.T) { + mock := NewMockOptimizer(true) + setMockEmpDeptForeignKeyAction(t, mock, plan.ForeignKeyDef_SET_NULL, plan.ForeignKeyDef_CASCADE) + mock.ctxt.ResolveVariableFunc = func(name string, _, _ bool) (interface{}, error) { + switch name { + case "foreign_key_checks": + return int64(0), nil + case "sql_mode": + return "", nil + default: + return nil, moerr.NewInternalError(context.Background(), "unexpected variable") + } + } + + query := buildPreparedQuery(t, mock, "prepare stmt1 from update emp set deptno = ? where empno = ?") + require.True(t, query.GetHasForeignKeyAction()) + }) + t.Run("parent delete set null marks prepare uncacheable", func(t *testing.T) { mock := NewMockOptimizer(true) setMockEmpDeptForeignKeyAction(t, mock, plan.ForeignKeyDef_SET_NULL, plan.ForeignKeyDef_RESTRICT) diff --git a/test/distributed/cases/prepare/prepare_fk_action_cache.result b/test/distributed/cases/prepare/prepare_fk_action_cache.result index c2896d8d572ec..020bc7bd308e0 100644 --- a/test/distributed/cases/prepare/prepare_fk_action_cache.result +++ b/test/distributed/cases/prepare/prepare_fk_action_cache.result @@ -35,4 +35,22 @@ select id, parent_id from child_delete order by id; id parent_id 10 NULL 20 NULL +create table parent_toggle(id int primary key); +create table child_toggle( +id int primary key, +parent_id int, +foreign key(parent_id) references parent_toggle(id) +); +insert into parent_toggle values (1); +insert into child_toggle values (10, 1); +set foreign_key_checks = 0; +prepare stmt_toggle_fk from 'update child_toggle set parent_id = ? where id = 10'; +set foreign_key_checks = 1; +set @missing_parent_id = 2; +execute stmt_toggle_fk using @missing_parent_id; +internal error: Cannot add or update a child row: a foreign key constraint fails +deallocate prepare stmt_toggle_fk; +select id, parent_id from child_toggle; +id parent_id +10 1 drop database prepare_fk_action_cache; diff --git a/test/distributed/cases/prepare/prepare_fk_action_cache.sql b/test/distributed/cases/prepare/prepare_fk_action_cache.sql index b6705e84704d1..7556f870a042c 100644 --- a/test/distributed/cases/prepare/prepare_fk_action_cache.sql +++ b/test/distributed/cases/prepare/prepare_fk_action_cache.sql @@ -33,4 +33,20 @@ deallocate prepare stmt_delete_fk; select id, v from parent_delete order by id; select id, parent_id from child_delete order by id; +create table parent_toggle(id int primary key); +create table child_toggle( + id int primary key, + parent_id int, + foreign key(parent_id) references parent_toggle(id) +); +insert into parent_toggle values (1); +insert into child_toggle values (10, 1); +set foreign_key_checks = 0; +prepare stmt_toggle_fk from 'update child_toggle set parent_id = ? where id = 10'; +set foreign_key_checks = 1; +set @missing_parent_id = 2; +execute stmt_toggle_fk using @missing_parent_id; +deallocate prepare stmt_toggle_fk; +select id, parent_id from child_toggle; + drop database prepare_fk_action_cache; From 1680d36cf632ab2c27aaf2b2b0dc67c0fee742eb Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Fri, 31 Jul 2026 16:42:52 +0800 Subject: [PATCH 07/22] fix(plan): preserve typed update FK errors --- pkg/sql/plan/bind_update_fk.go | 5 ++++- pkg/sql/plan/update_planner_route_test.go | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/pkg/sql/plan/bind_update_fk.go b/pkg/sql/plan/bind_update_fk.go index 149faa5eda1b9..b3f560d922594 100644 --- a/pkg/sql/plan/bind_update_fk.go +++ b/pkg/sql/plan/bind_update_fk.go @@ -23,6 +23,8 @@ import ( "github.com/matrixorigin/matrixone/pkg/pb/plan" ) +const foreignKeyNoReferencedRowAssert = "fk_no_referenced_row" + func (builder *QueryBuilder) updateInputProjectNode(nodeID int32) *plan.Node { node := builder.qry.Nodes[nodeID] if node.NodeType == plan.Node_PRE_INSERT && len(node.Children) == 1 { @@ -116,6 +118,7 @@ func (builder *QueryBuilder) appendUpdateForeignKeyChecks( errExpr := makePlan2StringConstExprWithType( "Cannot add or update a child row: a foreign key constraint fails", ) + errTypeExpr := makePlan2StringConstExprWithType(foreignKeyNoReferencedRowAssert) for j, fk := range validatedFks { unchanged, buildErr := builder.buildUpdateFkUnchangedExpr( tableDef, @@ -143,7 +146,7 @@ func (builder *QueryBuilder) appendUpdateForeignKeyChecks( assertConds[j], err = BindFuncExprImplByPlanExpr( builder.GetContext(), "assert", - []*plan.Expr{ok, DeepCopyExpr(errExpr)}, + []*plan.Expr{ok, DeepCopyExpr(errExpr), DeepCopyExpr(errTypeExpr)}, ) if err != nil { return 0, 0, nil, err diff --git a/pkg/sql/plan/update_planner_route_test.go b/pkg/sql/plan/update_planner_route_test.go index ca1b5d98a0adf..7a1b56457815a 100644 --- a/pkg/sql/plan/update_planner_route_test.go +++ b/pkg/sql/plan/update_planner_route_test.go @@ -430,6 +430,10 @@ func TestBindUpdateForeignKeyRoutingByAffectedColumns(t *testing.T) { require.NoError(t, err) require.Equal(t, 1, countUpdateFkMarkJoins(logicPlan.GetQuery())) require.Equal(t, 1, countUpdateFkAsserts(logicPlan.GetQuery())) + require.True(t, updateFkPlanContainsTypedAssert( + logicPlan.GetQuery(), + foreignKeyNoReferencedRowAssert, + )) require.True(t, updateFkPlanContainsFunc(logicPlan.GetQuery(), "<=>")) }) @@ -691,6 +695,21 @@ func countUpdateFkAsserts(query *planpb.Query) int { return count } +func updateFkPlanContainsTypedAssert(query *planpb.Query, errType string) bool { + for _, node := range query.Nodes { + for _, filter := range node.FilterList { + fn := filter.GetF() + if fn == nil || fn.Func.ObjName != "assert" || len(fn.Args) != 3 { + continue + } + if lit := fn.Args[2].GetLit(); lit != nil && lit.GetSval() == errType { + return true + } + } + } + return false +} + func updateFkPlanContainsFunc(query *planpb.Query, name string) bool { var contains func(*planpb.Expr) bool contains = func(expr *planpb.Expr) bool { From 4927b1f02690ef355bfc5bea933ff5fb45401288 Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Fri, 31 Jul 2026 18:02:23 +0800 Subject: [PATCH 08/22] fix: preserve typed FK errors in legacy updates --- pkg/sql/plan/build_constraint_util.go | 7 ++++++- pkg/sql/plan/build_dml_util.go | 4 +++- pkg/sql/plan/update_planner_route_test.go | 18 ++++++++++++++++++ .../foreign_key/foreign_key_multilayer.result | 2 +- .../cases/foreign_key/update_modern_fk.result | 6 +++--- 5 files changed, 31 insertions(+), 6 deletions(-) diff --git a/pkg/sql/plan/build_constraint_util.go b/pkg/sql/plan/build_constraint_util.go index 31fd0bfead4e7..a8320eb5af356 100644 --- a/pkg/sql/plan/build_constraint_util.go +++ b/pkg/sql/plan/build_constraint_util.go @@ -1653,6 +1653,7 @@ func appendForeignConstrantPlan( objRef *ObjectRef, sourceStep int32, isFkRecursionCall bool, + isUpdate bool, ) error { enabled, err := IsForeignKeyChecksEnabled(builder.compCtx) if err != nil { @@ -1691,7 +1692,11 @@ func appendForeignConstrantPlan( if err != nil { return err } - filterExpr, err := BindFuncExprImplByPlanExpr(builder.GetContext(), "assert", []*Expr{nullCheckExpr, errExpr}) + assertArgs := []*Expr{nullCheckExpr, errExpr} + if isUpdate { + assertArgs = append(assertArgs, makePlan2StringConstExprWithType(foreignKeyNoReferencedRowAssert)) + } + filterExpr, err := BindFuncExprImplByPlanExpr(builder.GetContext(), "assert", assertArgs) if err != nil { return err } diff --git a/pkg/sql/plan/build_dml_util.go b/pkg/sql/plan/build_dml_util.go index 19127004ed7a8..a10c623b68e6d 100644 --- a/pkg/sql/plan/build_dml_util.go +++ b/pkg/sql/plan/build_dml_util.go @@ -2097,7 +2097,9 @@ func makeOneInsertPlan( // if table have fk. then append join node & filter node // sink_scan -> join -> filter - if err = appendForeignConstrantPlan(builder, bindCtx, tableDef, objRef, sourceStep, isFkRecursionCall); err != nil { + if err = appendForeignConstrantPlan( + builder, bindCtx, tableDef, objRef, sourceStep, isFkRecursionCall, updateColLength > 0, + ); err != nil { return err } diff --git a/pkg/sql/plan/update_planner_route_test.go b/pkg/sql/plan/update_planner_route_test.go index 7a1b56457815a..dc2ef60db9b8e 100644 --- a/pkg/sql/plan/update_planner_route_test.go +++ b/pkg/sql/plan/update_planner_route_test.go @@ -317,6 +317,24 @@ func TestBindUpdateForeignKeyRoutingByAffectedColumns(t *testing.T) { require.True(t, hasAssert) }) + t.Run("legacy child foreign key update keeps typed error", func(t *testing.T) { + mock := NewMockOptimizer(true) + prepareEmpDept(mock) + mock.ctxt.tables["emp"].Indexes = []*planpb.IndexDef{{ + IndexName: "idx", + IndexAlgo: catalog.MoIndexIvfFlatAlgo.ToString(), + Parts: []string{"sal"}, + }} + + logicPlan, err := runOneStmt(mock, t, "UPDATE emp SET deptno = 2, sal = 1") + require.NoError(t, err) + require.Equal(t, 0, countUpdateFkPlanNodes(logicPlan.GetQuery(), planpb.Node_MULTI_UPDATE)) + require.True(t, updateFkPlanContainsTypedAssert( + logicPlan.GetQuery(), + foreignKeyNoReferencedRowAssert, + )) + }) + t.Run("affected restricted parent key stays modern with child probe", func(t *testing.T) { mock := NewMockOptimizer(true) prepareEmpDept(mock) diff --git a/test/distributed/cases/foreign_key/foreign_key_multilayer.result b/test/distributed/cases/foreign_key/foreign_key_multilayer.result index 4d8c0569d3d49..540762bfac51c 100644 --- a/test/distributed/cases/foreign_key/foreign_key_multilayer.result +++ b/test/distributed/cases/foreign_key/foreign_key_multilayer.result @@ -318,7 +318,7 @@ select * from PARTSUPP_fk; ps_partkey ps_suppkey ps_availqty ps_supplycost ps_comment 199 9991 7872 606.64 according to the final pinto beans: carefully silent requests sleep final update PARTSUPP_fk set PS_PARTKEY=40; -internal error: Cannot add or update a child row: a foreign key constraint fails +Cannot add or update a child row: a foreign key constraint fails select * from lineitem_fk; l_orderkey l_partkey l_suppkey l_linenumber l_quantity l_extendedprice l_discount l_tax l_returnflag l_linestatus l_shipdate l_commitdate l_receiptdate l_shipinstruct l_shipmode l_comment 5999968 199 9991 2 46.00 63179.16 0.08 0.06 R F 1993-09-16 1993-09-21 1993-10-02 COLLECT COD RAIL dolites wake diff --git a/test/distributed/cases/foreign_key/update_modern_fk.result b/test/distributed/cases/foreign_key/update_modern_fk.result index 9e75bf82dfdd7..8f68a13958909 100644 --- a/test/distributed/cases/foreign_key/update_modern_fk.result +++ b/test/distributed/cases/foreign_key/update_modern_fk.result @@ -22,7 +22,7 @@ select * from child_single order by id; 10 ¦ 3 ¦ b 𝄀 20 ¦ 2 ¦ null update child_single set parent_id = 99 where id = 10; -internal error: Cannot add or update a child row: a foreign key constraint fails +Cannot add or update a child row: a foreign key constraint fails select * from child_single order by id; ➤ id[4,32,0] ¦ parent_id[4,32,0] ¦ note[12,-1,0] 𝄀 10 ¦ 3 ¦ b 𝄀 @@ -40,7 +40,7 @@ select * from child_single order by id; 20 ¦ 2 ¦ null update child_single set parent_id = 1 where id = 10; update child_single set parent_id = case id when 10 then 2 else 98 end; -internal error: Cannot add or update a child row: a foreign key constraint fails +Cannot add or update a child row: a foreign key constraint fails select * from child_single order by id; ➤ id[4,32,0] ¦ parent_id[4,32,0] ¦ note[12,-1,0] 𝄀 10 ¦ 1 ¦ orphan 𝄀 @@ -67,7 +67,7 @@ select * from child_composite order by id; 2 ¦ 99 ¦ null 𝄀 3 ¦ 1 ¦ 1 update child_composite set a = 98, b = 98 where id = 3; -internal error: Cannot add or update a child row: a foreign key constraint fails +Cannot add or update a child row: a foreign key constraint fails select * from child_composite order by id; ➤ id[4,32,0] ¦ a[4,32,0] ¦ b[4,32,0] 𝄀 1 ¦ null ¦ 99 𝄀 From f7c5e177df4a6c343f6f1fc2880d2fd089ea9ffb Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Fri, 31 Jul 2026 22:01:28 +0800 Subject: [PATCH 09/22] test: update prepared FK error result --- test/distributed/cases/prepare/prepare_fk_action_cache.result | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/distributed/cases/prepare/prepare_fk_action_cache.result b/test/distributed/cases/prepare/prepare_fk_action_cache.result index 020bc7bd308e0..df9c314bf6399 100644 --- a/test/distributed/cases/prepare/prepare_fk_action_cache.result +++ b/test/distributed/cases/prepare/prepare_fk_action_cache.result @@ -48,7 +48,7 @@ prepare stmt_toggle_fk from 'update child_toggle set parent_id = ? where id = 10 set foreign_key_checks = 1; set @missing_parent_id = 2; execute stmt_toggle_fk using @missing_parent_id; -internal error: Cannot add or update a child row: a foreign key constraint fails +Cannot add or update a child row: a foreign key constraint fails deallocate prepare stmt_toggle_fk; select id, parent_id from child_toggle; id parent_id From 1a6f76b813573424f7dce04a195ac258b69ea31c Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Fri, 31 Jul 2026 23:05:21 +0800 Subject: [PATCH 10/22] test: expand FK update planner coverage --- pkg/sql/plan/update_planner_route_test.go | 61 ++++++++++++++++++++++- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/pkg/sql/plan/update_planner_route_test.go b/pkg/sql/plan/update_planner_route_test.go index dc2ef60db9b8e..f84577d9373a5 100644 --- a/pkg/sql/plan/update_planner_route_test.go +++ b/pkg/sql/plan/update_planner_route_test.go @@ -315,6 +315,10 @@ func TestBindUpdateForeignKeyRoutingByAffectedColumns(t *testing.T) { require.True(t, hasParentScan) require.True(t, hasMarkJoin) require.True(t, hasAssert) + require.True(t, updateFkPlanContainsTypedAssert( + logicPlan.GetQuery(), + foreignKeyNoReferencedRowAssert, + )) }) t.Run("legacy child foreign key update keeps typed error", func(t *testing.T) { @@ -328,9 +332,12 @@ func TestBindUpdateForeignKeyRoutingByAffectedColumns(t *testing.T) { logicPlan, err := runOneStmt(mock, t, "UPDATE emp SET deptno = 2, sal = 1") require.NoError(t, err) - require.Equal(t, 0, countUpdateFkPlanNodes(logicPlan.GetQuery(), planpb.Node_MULTI_UPDATE)) + query := logicPlan.GetQuery() + require.Equal(t, 0, countUpdateFkPlanNodes(query, planpb.Node_MULTI_UPDATE)) + require.Equal(t, 1, countUpdateFkAsserts(query)) + require.True(t, updateFkPlanScansTable(query, mock.ctxt.tables["dept"].TblId)) require.True(t, updateFkPlanContainsTypedAssert( - logicPlan.GetQuery(), + query, foreignKeyNoReferencedRowAssert, )) }) @@ -604,6 +611,10 @@ func TestBindUpdateAutoIncrementRunsBeforeForeignKeys(t *testing.T) { require.NotEqual(t, -1, preInsert) require.NotEqual(t, -1, markJoin) require.Less(t, preInsert, markJoin) + require.True(t, updateFkPlanContainsTypedAssert( + query, + foreignKeyNoReferencedRowAssert, + )) }) t.Run("parent action on generated key uses legacy planner", func(t *testing.T) { @@ -646,6 +657,31 @@ func TestBindUpdateAutoIncrementRunsBeforeForeignKeys(t *testing.T) { }) } +func TestLegacyInsertForeignKeyKeepsGenericAssert(t *testing.T) { + mock := NewMockOptimizer(true) + emp := mock.ctxt.tables["emp"] + dept := mock.ctxt.tables["dept"] + emp.TblId = 88887 + emp.Fkeys[0].ForeignTbl = dept.TblId + emp.Fkeys[0].ForeignCols = []uint64{0} + mock.ctxt.id2name[emp.TblId] = "emp" + + stmt, err := parsers.ParseOne( + mock.CurrentContext().GetContext(), + dialect.MYSQL, + "INSERT INTO emp (empno, deptno) VALUES (1, 10)", + 1, + ) + require.NoError(t, err) + defer stmt.Free() + + logicPlan, err := buildInsert(stmt.(*tree.Insert), mock.CurrentContext(), false, false) + require.NoError(t, err) + query := logicPlan.GetQuery() + require.True(t, updateFkPlanContainsAssertWithArity(query, 2)) + require.False(t, updateFkPlanContainsTypedAssert(query, foreignKeyNoReferencedRowAssert)) +} + func TestResolveSingleTablePreservesForeignKeyPolicy(t *testing.T) { mock := NewMockOptimizer(true) stmt, err := parsers.ParseOne( @@ -728,6 +764,27 @@ func updateFkPlanContainsTypedAssert(query *planpb.Query, errType string) bool { return false } +func updateFkPlanContainsAssertWithArity(query *planpb.Query, arity int) bool { + for _, node := range query.Nodes { + for _, filter := range node.FilterList { + fn := filter.GetF() + if fn != nil && fn.Func.ObjName == "assert" && len(fn.Args) == arity { + return true + } + } + } + return false +} + +func updateFkPlanScansTable(query *planpb.Query, tableID uint64) bool { + for _, node := range query.Nodes { + if node.NodeType == planpb.Node_TABLE_SCAN && node.TableDef != nil && node.TableDef.TblId == tableID { + return true + } + } + return false +} + func updateFkPlanContainsFunc(query *planpb.Query, name string) bool { var contains func(*planpb.Expr) bool contains = func(expr *planpb.Expr) bool { From 095ae78daeec32db474cf0a08458ebd6a5e05f15 Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Sat, 1 Aug 2026 00:19:26 +0800 Subject: [PATCH 11/22] test: cover generated columns in FK cascades --- pkg/sql/plan/update_planner_route_test.go | 32 +++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/pkg/sql/plan/update_planner_route_test.go b/pkg/sql/plan/update_planner_route_test.go index f84577d9373a5..fdd75a2cb4f3a 100644 --- a/pkg/sql/plan/update_planner_route_test.go +++ b/pkg/sql/plan/update_planner_route_test.go @@ -410,6 +410,38 @@ func TestBindUpdateForeignKeyRoutingByAffectedColumns(t *testing.T) { }) } + t.Run("cascade recomputes generated child column from new foreign key", func(t *testing.T) { + mock := NewMockOptimizer(true) + prepareEmpDept(mock) + setMockGeneratedColumn(t, mock, "emp", "sal", "deptno") + mock.ctxt.tables["emp"].Fkeys[0].OnUpdate = planpb.ForeignKeyDef_CASCADE + + logicPlan, err := runOneStmt(mock, t, "UPDATE dept SET deptno = 2") + require.NoError(t, err) + query := logicPlan.GetQuery() + require.Equal(t, 2, countUpdateFkPlanNodes(query, planpb.Node_MULTI_UPDATE)) + + emp := mock.ctxt.tables["emp"] + salPos := emp.Name2ColIndex["sal"] + deptnoPos := emp.Name2ColIndex["deptno"] + hasRecomputedSal := false + projectPairs := make([][2]string, 0) + for _, node := range query.Nodes { + if node.NodeType != planpb.Node_PROJECT || len(node.ProjectList) < len(emp.Cols) { + continue + } + projectPairs = append(projectPairs, [2]string{ + node.ProjectList[salPos].String(), + node.ProjectList[deptnoPos].String(), + }) + if node.ProjectList[salPos].String() == node.ProjectList[deptnoPos].String() { + hasRecomputedSal = true + break + } + } + require.True(t, hasRecomputedSal, "candidate sal/deptno projections: %v", projectPairs) + }) + t.Run("disabled checks skip child probe and parent fallback", func(t *testing.T) { mock := NewMockOptimizer(true) prepareEmpDept(mock) From 656b9ecd2df3b20bac37fcf7459c478c488d8aa8 Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Sat, 1 Aug 2026 00:21:50 +0800 Subject: [PATCH 12/22] test: align FK route coverage with modern index updates --- pkg/sql/plan/update_planner_route_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/sql/plan/update_planner_route_test.go b/pkg/sql/plan/update_planner_route_test.go index 2e2bdb1ce3819..4db4f78775cec 100644 --- a/pkg/sql/plan/update_planner_route_test.go +++ b/pkg/sql/plan/update_planner_route_test.go @@ -328,7 +328,7 @@ func TestBindUpdateForeignKeyRoutingByAffectedColumns(t *testing.T) { )) }) - t.Run("legacy child foreign key update keeps typed error", func(t *testing.T) { + t.Run("irregular index child foreign key update keeps typed error", func(t *testing.T) { mock := NewMockOptimizer(true) prepareEmpDept(mock) mock.ctxt.tables["emp"].Indexes = []*planpb.IndexDef{{ @@ -340,7 +340,7 @@ func TestBindUpdateForeignKeyRoutingByAffectedColumns(t *testing.T) { logicPlan, err := runOneStmt(mock, t, "UPDATE emp SET deptno = 2, sal = 1") require.NoError(t, err) query := logicPlan.GetQuery() - require.Equal(t, 0, countUpdateFkPlanNodes(query, planpb.Node_MULTI_UPDATE)) + require.Equal(t, 1, countUpdateFkPlanNodes(query, planpb.Node_MULTI_UPDATE)) require.Equal(t, 1, countUpdateFkAsserts(query)) require.True(t, updateFkPlanScansTable(query, mock.ctxt.tables["dept"].TblId)) require.True(t, updateFkPlanContainsTypedAssert( From 957a6ecd770d71d87e33732600ad748407fbc3e1 Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Sat, 1 Aug 2026 18:40:16 +0800 Subject: [PATCH 13/22] fix: gate incomplete FK cascade row closure --- pkg/sql/plan/bind_update_fk.go | 91 +++++++++++++++---- pkg/sql/plan/update_planner_route_test.go | 87 ++++++++++++------ .../cases/foreign_key/update_modern_fk.result | 25 +++++ .../cases/foreign_key/update_modern_fk.sql | 19 ++++ 4 files changed, 174 insertions(+), 48 deletions(-) diff --git a/pkg/sql/plan/bind_update_fk.go b/pkg/sql/plan/bind_update_fk.go index b3f560d922594..9d16f91d4e11e 100644 --- a/pkg/sql/plan/bind_update_fk.go +++ b/pkg/sql/plan/bind_update_fk.go @@ -21,6 +21,7 @@ import ( "github.com/matrixorigin/matrixone/pkg/catalog" "github.com/matrixorigin/matrixone/pkg/common/moerr" "github.com/matrixorigin/matrixone/pkg/pb/plan" + planutil "github.com/matrixorigin/matrixone/pkg/sql/util" ) const foreignKeyNoReferencedRowAssert = "fk_no_referenced_row" @@ -398,6 +399,9 @@ func (builder *QueryBuilder) validateModernUpdateParentMutation( ) } } + if err := builder.validateModernUpdateParentRowClosure(parentTableDef, affectedFK); err != nil { + return err + } if childTableDef.TblId == parentTableDef.TblId { return newLegacyUpdatePlannerRouteError( updateRouteReasonForeignKey, @@ -493,6 +497,74 @@ func (builder *QueryBuilder) validateModernUpdateParentMutation( return nil } +func (builder *QueryBuilder) validateModernUpdateParentRowClosure( + parentTableDef *plan.TableDef, + affectedFK updateParentForeignKey, +) error { + childTableDef := affectedFK.childTableDef + childColByID := make(map[uint64]*plan.ColDef, len(childTableDef.Cols)) + parentColByID := make(map[uint64]*plan.ColDef, len(parentTableDef.Cols)) + updatedChildNames := make(map[string]struct{}, len(affectedFK.fk.Cols)) + for _, col := range childTableDef.Cols { + childColByID[col.ColId] = col + if col.GeneratedCol != nil || col.OnUpdate != nil { + return builder.newLegacyUpdateParentRowClosureError() + } + } + for _, col := range parentTableDef.Cols { + parentColByID[col.ColId] = col + } + for i, childColID := range affectedFK.fk.Cols { + childCol := childColByID[childColID] + if childCol == nil || i >= len(affectedFK.fk.ForeignCols) { + return moerr.NewInternalError(builder.GetContext(), "invalid parent foreign key action columns") + } + updatedChildNames[childCol.Name] = struct{}{} + if affectedFK.fk.OnUpdate != plan.ForeignKeyDef_CASCADE { + continue + } + parentCol := parentColByID[affectedFK.fk.ForeignCols[i]] + if parentCol == nil { + return moerr.NewInternalError(builder.GetContext(), "invalid parent foreign key action columns") + } + if parentCol.Typ.Id != childCol.Typ.Id || + parentCol.Typ.Width != childCol.Typ.Width || + parentCol.Typ.Scale != childCol.Typ.Scale || + parentCol.Typ.Enumvalues != childCol.Typ.Enumvalues { + return builder.newLegacyUpdateParentRowClosureError() + } + } + for _, idxDef := range childTableDef.Indexes { + if !idxDef.Unique { + continue + } + for _, part := range idxDef.Parts { + if _, changed := updatedChildNames[catalog.ResolveAlias(part)]; changed { + return builder.newLegacyUpdateParentRowClosureError() + } + } + } + if childTableDef.ClusterBy != nil && + planutil.JudgeIsCompositeClusterByColumn(childTableDef.ClusterBy.Name) { + for _, colName := range planutil.SplitCompositeClusterByColumnName(childTableDef.ClusterBy.Name) { + if _, changed := updatedChildNames[colName]; changed { + return builder.newLegacyUpdateParentRowClosureError() + } + } + } + return nil +} + +func (builder *QueryBuilder) newLegacyUpdateParentRowClosureError() error { + return newLegacyUpdatePlannerRouteError( + updateRouteReasonForeignKey, + moerr.NewUnsupportedDML( + builder.GetContext(), + "parent foreign key action requires complete child update row closure", + ), + ) +} + func (builder *QueryBuilder) appendUpdateParentMutation( bindCtx *BindContext, parentTableDef *plan.TableDef, @@ -706,29 +778,10 @@ func (builder *QueryBuilder) appendUpdateParentMutation( }}, } } - for i, col := range childTableDef.Cols { - if col.GeneratedCol == nil { - continue - } - generatedExpr := builder.applyGeneratedColumnAssignmentCast( - DeepCopyExpr(col.GeneratedCol.Expr), - false, - ) - actionProjection[i] = substituteColRefsInExpr( - generatedExpr, - actionProjection, - 0, - ) - } finalReplacements := make(map[int32]*plan.Expr, len(newChildExprs)) for pos, expr := range newChildExprs { finalReplacements[pos] = expr } - for i, col := range childTableDef.Cols { - if col.GeneratedCol != nil { - finalReplacements[int32(i)] = actionProjection[i] - } - } type mutationIndexPositions struct { oldRowID int32 diff --git a/pkg/sql/plan/update_planner_route_test.go b/pkg/sql/plan/update_planner_route_test.go index 4db4f78775cec..08873ce28b4df 100644 --- a/pkg/sql/plan/update_planner_route_test.go +++ b/pkg/sql/plan/update_planner_route_test.go @@ -28,6 +28,7 @@ import ( "github.com/matrixorigin/matrixone/pkg/sql/parsers" "github.com/matrixorigin/matrixone/pkg/sql/parsers/dialect" "github.com/matrixorigin/matrixone/pkg/sql/parsers/tree" + planutil "github.com/matrixorigin/matrixone/pkg/sql/util" ) func TestClassifyUpdatePlannerError(t *testing.T) { @@ -417,37 +418,65 @@ func TestBindUpdateForeignKeyRoutingByAffectedColumns(t *testing.T) { }) } - t.Run("cascade recomputes generated child column from new foreign key", func(t *testing.T) { - mock := NewMockOptimizer(true) - prepareEmpDept(mock) - setMockGeneratedColumn(t, mock, "emp", "sal", "deptno") - mock.ctxt.tables["emp"].Fkeys[0].OnUpdate = planpb.ForeignKeyDef_CASCADE + for _, test := range []struct { + name string + prepare func(*MockOptimizer) + }{ + { + name: "generated unique collision requires complete child row closure", + prepare: func(mock *MockOptimizer) { + setMockGeneratedColumn(t, mock, "emp", "sal", "deptno") + mock.ctxt.tables["emp"].Indexes = append(mock.ctxt.tables["emp"].Indexes, &planpb.IndexDef{ + IndexName: "uk_sal", Parts: []string{"sal"}, Unique: true, + }) + }, + }, + { + name: "parent child width mismatch requires assignment cast", + prepare: func(mock *MockOptimizer) { + requireMockColumn(t, mock, "emp", "deptno").Typ.Width = 16 + }, + }, + { + name: "composite cluster by dependency requires recomputation", + prepare: func(mock *MockOptimizer) { + emp := mock.ctxt.tables["emp"] + emp.ClusterBy = &planpb.ClusterByDef{ + Name: planutil.BuildCompositeClusterByColumnName([]string{"deptno", "empno"}), + } + }, + }, + { + name: "on update feeding generated column requires dependency closure", + prepare: func(mock *MockOptimizer) { + setMockOnUpdateExpr(t, mock, "emp", "job", "changed") + setMockGeneratedColumn(t, mock, "emp", "ename", "job") + }, + }, + } { + t.Run(test.name, func(t *testing.T) { + mock := NewMockOptimizer(true) + prepareEmpDept(mock) + mock.ctxt.tables["emp"].Fkeys[0].OnUpdate = planpb.ForeignKeyDef_CASCADE + test.prepare(mock) - logicPlan, err := runOneStmt(mock, t, "UPDATE dept SET deptno = 2") - require.NoError(t, err) - query := logicPlan.GetQuery() - require.Equal(t, 2, countUpdateFkPlanNodes(query, planpb.Node_MULTI_UPDATE)) + stmt, err := parsers.ParseOne( + mock.CurrentContext().GetContext(), + dialect.MYSQL, + "UPDATE dept SET deptno = 2", + 1, + ) + require.NoError(t, err) + defer stmt.Free() - emp := mock.ctxt.tables["emp"] - salPos := emp.Name2ColIndex["sal"] - deptnoPos := emp.Name2ColIndex["deptno"] - hasRecomputedSal := false - projectPairs := make([][2]string, 0) - for _, node := range query.Nodes { - if node.NodeType != planpb.Node_PROJECT || len(node.ProjectList) < len(emp.Cols) { - continue - } - projectPairs = append(projectPairs, [2]string{ - node.ProjectList[salPos].String(), - node.ProjectList[deptnoPos].String(), - }) - if node.ProjectList[salPos].String() == node.ProjectList[deptnoPos].String() { - hasRecomputedSal = true - break - } - } - require.True(t, hasRecomputedSal, "candidate sal/deptno projections: %v", projectPairs) - }) + builder := NewQueryBuilder(planpb.Query_UPDATE, mock.CurrentContext(), false, true) + _, err = builder.bindUpdate(stmt.(*tree.Update), NewBindContext(builder, nil)) + require.ErrorContains(t, err, "complete child update row closure") + route, reason, _ := classifyUpdatePlannerError(err) + require.Equal(t, updatePlannerLegacy, route) + require.Equal(t, updateRouteReasonForeignKey, reason) + }) + } t.Run("disabled checks skip child probe and parent fallback", func(t *testing.T) { mock := NewMockOptimizer(true) diff --git a/test/distributed/cases/foreign_key/update_modern_fk.result b/test/distributed/cases/foreign_key/update_modern_fk.result index 8f68a13958909..0dceb8458ad17 100644 --- a/test/distributed/cases/foreign_key/update_modern_fk.result +++ b/test/distributed/cases/foreign_key/update_modern_fk.result @@ -257,6 +257,29 @@ select * from parent_dual_a order by id; select * from child_dual_fk; ➤ x[4,32,0] 𝄀 1 +create table parent_generated_unique ( +id int primary key +); +create table child_generated_unique ( +id int primary key, +parent_id int, +u int generated always as (parent_id % 10) stored, +unique key uk_generated_unique(u), +constraint fk_generated_unique foreign key (parent_id) +references parent_generated_unique(id) on update cascade +); +insert into parent_generated_unique values (1), (12); +insert into child_generated_unique(id, parent_id) values (10, 1), (20, 12); +update parent_generated_unique set id = 2 where id = 1; +Duplicate entry '2' for key 'u' +select * from parent_generated_unique order by id; +➤ id[4,32,0] 𝄀 +1 𝄀 +12 +select * from child_generated_unique order by id; +➤ id[4,32,0] ¦ parent_id[4,32,0] ¦ u[4,32,0] 𝄀 +10 ¦ 1 ¦ 1 𝄀 +20 ¦ 12 ¦ 2 set foreign_key_checks = 0; create table update_preinsert_index ( a int not null auto_increment primary key, @@ -271,6 +294,8 @@ update update_preinsert_index set a = 90; Duplicate entry '90' for key 'a' set foreign_key_checks = 1; drop table update_preinsert_index; +drop table child_generated_unique; +drop table parent_generated_unique; drop table child_dual_fk; drop table parent_dual_b; drop table parent_dual_a; diff --git a/test/distributed/cases/foreign_key/update_modern_fk.sql b/test/distributed/cases/foreign_key/update_modern_fk.sql index 62ec3112e1e19..c86768dbae605 100644 --- a/test/distributed/cases/foreign_key/update_modern_fk.sql +++ b/test/distributed/cases/foreign_key/update_modern_fk.sql @@ -203,6 +203,23 @@ update parent_dual_a set id = 2 where id = 1; select * from parent_dual_a order by id; select * from child_dual_fk; +create table parent_generated_unique ( + id int primary key +); +create table child_generated_unique ( + id int primary key, + parent_id int, + u int generated always as (parent_id % 10) stored, + unique key uk_generated_unique(u), + constraint fk_generated_unique foreign key (parent_id) + references parent_generated_unique(id) on update cascade +); +insert into parent_generated_unique values (1), (12); +insert into child_generated_unique(id, parent_id) values (10, 1), (20, 12); +update parent_generated_unique set id = 2 where id = 1; +select * from parent_generated_unique order by id; +select * from child_generated_unique order by id; + set foreign_key_checks = 0; create table update_preinsert_index ( a int not null auto_increment primary key, @@ -217,6 +234,8 @@ update update_preinsert_index set a = 90; set foreign_key_checks = 1; drop table update_preinsert_index; +drop table child_generated_unique; +drop table parent_generated_unique; drop table child_dual_fk; drop table parent_dual_b; drop table parent_dual_a; From 8968a53dd8ae27611704258e574c48b4f4aebac8 Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Sat, 1 Aug 2026 18:51:40 +0800 Subject: [PATCH 14/22] fix: reject unsafe derived FK cascades --- pkg/sql/plan/bind_update_fk.go | 19 ++++++++----------- pkg/sql/plan/update_planner_route_test.go | 5 ++--- .../cases/foreign_key/update_modern_fk.result | 11 ++++++----- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/pkg/sql/plan/bind_update_fk.go b/pkg/sql/plan/bind_update_fk.go index 9d16f91d4e11e..de60057dd4537 100644 --- a/pkg/sql/plan/bind_update_fk.go +++ b/pkg/sql/plan/bind_update_fk.go @@ -508,7 +508,7 @@ func (builder *QueryBuilder) validateModernUpdateParentRowClosure( for _, col := range childTableDef.Cols { childColByID[col.ColId] = col if col.GeneratedCol != nil || col.OnUpdate != nil { - return builder.newLegacyUpdateParentRowClosureError() + return builder.newUnsupportedUpdateParentRowClosureError() } } for _, col := range parentTableDef.Cols { @@ -531,7 +531,7 @@ func (builder *QueryBuilder) validateModernUpdateParentRowClosure( parentCol.Typ.Width != childCol.Typ.Width || parentCol.Typ.Scale != childCol.Typ.Scale || parentCol.Typ.Enumvalues != childCol.Typ.Enumvalues { - return builder.newLegacyUpdateParentRowClosureError() + return builder.newUnsupportedUpdateParentRowClosureError() } } for _, idxDef := range childTableDef.Indexes { @@ -540,7 +540,7 @@ func (builder *QueryBuilder) validateModernUpdateParentRowClosure( } for _, part := range idxDef.Parts { if _, changed := updatedChildNames[catalog.ResolveAlias(part)]; changed { - return builder.newLegacyUpdateParentRowClosureError() + return builder.newUnsupportedUpdateParentRowClosureError() } } } @@ -548,20 +548,17 @@ func (builder *QueryBuilder) validateModernUpdateParentRowClosure( planutil.JudgeIsCompositeClusterByColumn(childTableDef.ClusterBy.Name) { for _, colName := range planutil.SplitCompositeClusterByColumnName(childTableDef.ClusterBy.Name) { if _, changed := updatedChildNames[colName]; changed { - return builder.newLegacyUpdateParentRowClosureError() + return builder.newUnsupportedUpdateParentRowClosureError() } } } return nil } -func (builder *QueryBuilder) newLegacyUpdateParentRowClosureError() error { - return newLegacyUpdatePlannerRouteError( - updateRouteReasonForeignKey, - moerr.NewUnsupportedDML( - builder.GetContext(), - "parent foreign key action requires complete child update row closure", - ), +func (builder *QueryBuilder) newUnsupportedUpdateParentRowClosureError() error { + return moerr.NewNotSupported( + builder.GetContext(), + "parent foreign key action requires complete child update row closure", ) } diff --git a/pkg/sql/plan/update_planner_route_test.go b/pkg/sql/plan/update_planner_route_test.go index 08873ce28b4df..608d2ea1550c7 100644 --- a/pkg/sql/plan/update_planner_route_test.go +++ b/pkg/sql/plan/update_planner_route_test.go @@ -472,9 +472,8 @@ func TestBindUpdateForeignKeyRoutingByAffectedColumns(t *testing.T) { builder := NewQueryBuilder(planpb.Query_UPDATE, mock.CurrentContext(), false, true) _, err = builder.bindUpdate(stmt.(*tree.Update), NewBindContext(builder, nil)) require.ErrorContains(t, err, "complete child update row closure") - route, reason, _ := classifyUpdatePlannerError(err) - require.Equal(t, updatePlannerLegacy, route) - require.Equal(t, updateRouteReasonForeignKey, reason) + route, _, _ := classifyUpdatePlannerError(err) + require.Equal(t, updatePlannerRejected, route) }) } diff --git a/test/distributed/cases/foreign_key/update_modern_fk.result b/test/distributed/cases/foreign_key/update_modern_fk.result index 0dceb8458ad17..81c68ed001e6f 100644 --- a/test/distributed/cases/foreign_key/update_modern_fk.result +++ b/test/distributed/cases/foreign_key/update_modern_fk.result @@ -188,16 +188,17 @@ references parent_composite_action(a, b) on update cascade insert into parent_composite_action values (1, 2), (3, 4); insert into child_composite_action(id, a, b) values (10, 1, 2), (20, 3, 4); update parent_composite_action set a = 11, b = 12 where a = 1 and b = 2; +not supported: parent foreign key action requires complete child update row closure select row_count(); ➤ row_count()[-5,64,0] 𝄀 -1 +0 select * from parent_composite_action order by a, b; ➤ a[4,32,0] ¦ b[4,32,0] 𝄀 -3 ¦ 4 𝄀 -11 ¦ 12 +1 ¦ 2 𝄀 +3 ¦ 4 select * from child_composite_action order by id; ➤ id[4,32,0] ¦ a[4,32,0] ¦ b[4,32,0] ¦ generated_sum[4,32,0] 𝄀 -10 ¦ 11 ¦ 12 ¦ 23 𝄀 +10 ¦ 1 ¦ 2 ¦ 3 𝄀 20 ¦ 3 ¦ 4 ¦ 7 create table child_second_cascade ( id int primary key, @@ -271,7 +272,7 @@ references parent_generated_unique(id) on update cascade insert into parent_generated_unique values (1), (12); insert into child_generated_unique(id, parent_id) values (10, 1), (20, 12); update parent_generated_unique set id = 2 where id = 1; -Duplicate entry '2' for key 'u' +not supported: parent foreign key action requires complete child update row closure select * from parent_generated_unique order by id; ➤ id[4,32,0] 𝄀 1 𝄀 From 9ad9251bec0936ad2a2022d96651b42b73572753 Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Sat, 1 Aug 2026 22:29:13 +0800 Subject: [PATCH 15/22] fix: lock parent keys before FK update actions --- pkg/sql/plan/bind_update.go | 59 ++++- pkg/sql/plan/bind_update_fk.go | 230 +++++++++++++++++- pkg/sql/plan/build.go | 2 +- pkg/sql/plan/build_test.go | 8 + pkg/sql/plan/function/list_builtIn.go | 3 + pkg/sql/plan/update_planner_route_test.go | 110 ++++++++- .../cases/foreign_key/update_modern_fk.result | 27 +- .../cases/foreign_key/update_modern_fk.sql | 25 ++ 8 files changed, 450 insertions(+), 14 deletions(-) diff --git a/pkg/sql/plan/bind_update.go b/pkg/sql/plan/bind_update.go index 85cdd549e3748..3e7f4e5f16cf9 100644 --- a/pkg/sql/plan/bind_update.go +++ b/pkg/sql/plan/bind_update.go @@ -86,8 +86,12 @@ func (builder *QueryBuilder) bindUpdate(stmt *tree.Update, bindCtx *BindContext) return 0, err } for i, tableDef := range dmlCtx.tableDefs { - if len(dmlCtx.updateCol2Expr[i]) > 0 && - (len(tableDef.Fkeys) > 0 || len(tableDef.RefChildTbls) > 0) { + affected, affectedErr := builder.updateActuallyAffectsForeignKey( + bindCtx, tableDef, dmlCtx.updateCol2Expr[i]) + if affectedErr != nil { + return 0, affectedErr + } + if affected { // FK validation and parent-side actions depend on the current // foreign_key_checks value. Mark the plan before that value is // inspected so a plan built with checks disabled cannot be reused @@ -1207,6 +1211,57 @@ func (builder *QueryBuilder) bindUpdate(stmt *tree.Update, bindCtx *BindContext) return lastNodeID, err } +func (builder *QueryBuilder) updateActuallyAffectsForeignKey( + bindCtx *BindContext, + tableDef *plan.TableDef, + updatedCols map[string]tree.Expr, +) (bool, error) { + if tableDef == nil || len(updatedCols) == 0 { + return false, nil + } + colIDToName := make(map[uint64]string, len(tableDef.Cols)) + for _, col := range tableDef.Cols { + colIDToName[col.ColId] = col.Name + } + for _, fk := range tableDef.Fkeys { + for _, colID := range fk.Cols { + if _, ok := updatedCols[colIDToName[colID]]; ok { + return true, nil + } + } + } + + visited := make(map[uint64]struct{}, len(tableDef.RefChildTbls)) + for _, childID := range tableDef.RefChildTbls { + if childID == 0 { + childID = tableDef.TblId + } + if _, ok := visited[childID]; ok { + continue + } + visited[childID] = struct{}{} + _, childDef, err := builder.compCtx.ResolveById(childID, bindCtx.snapshot) + if err != nil { + return false, err + } + if childDef == nil { + return false, moerr.NewInternalErrorf( + builder.GetContext(), "foreign-key child table %d not found", childID) + } + for _, fk := range childDef.Fkeys { + if fk.ForeignTbl != tableDef.TblId && !(fk.ForeignTbl == 0 && childDef.TblId == tableDef.TblId) { + continue + } + for _, parentColID := range fk.ForeignCols { + if _, ok := updatedCols[colIDToName[parentColID]]; ok { + return true, nil + } + } + } + } + return false, nil +} + func irregularIndexAffectedByUpdate( tableDef *plan.TableDef, idxDef *plan.IndexDef, diff --git a/pkg/sql/plan/bind_update_fk.go b/pkg/sql/plan/bind_update_fk.go index de60057dd4537..6b04a8b1eab53 100644 --- a/pkg/sql/plan/bind_update_fk.go +++ b/pkg/sql/plan/bind_update_fk.go @@ -15,16 +15,21 @@ package plan import ( + "context" "fmt" "sort" "github.com/matrixorigin/matrixone/pkg/catalog" "github.com/matrixorigin/matrixone/pkg/common/moerr" + lockpb "github.com/matrixorigin/matrixone/pkg/pb/lock" "github.com/matrixorigin/matrixone/pkg/pb/plan" planutil "github.com/matrixorigin/matrixone/pkg/sql/util" ) -const foreignKeyNoReferencedRowAssert = "fk_no_referenced_row" +const ( + foreignKeyNoReferencedRowAssert = "fk_no_referenced_row" + foreignKeyRowIsReferencedAssert = "fk_row_is_referenced" +) func (builder *QueryBuilder) updateInputProjectNode(nodeID int32) *plan.Node { node := builder.qry.Nodes[nodeID] @@ -284,6 +289,23 @@ func (builder *QueryBuilder) appendUpdateParentForeignKeyChecks( } return affected[i].fk.Name < affected[j].fk.Name }) + if len(affected) == 0 { + return lastNodeID, selectNodeTag, nil + } + + lastNodeID, selectNodeTag, err := builder.appendUpdateParentKeyLocks( + bindCtx, + tableDef, + alias, + affected, + lastNodeID, + selectNodeTag, + oldColName2Idx, + newColName2Idx, + ) + if err != nil { + return 0, 0, err + } for _, affectedFK := range affected { if affectedFK.childTableDef.TblId == tableDef.TblId { @@ -377,6 +399,193 @@ func (builder *QueryBuilder) appendUpdateParentForeignKeyChecks( return lastNodeID, selectNodeTag, nil } +func (builder *QueryBuilder) appendUpdateParentKeyLocks( + bindCtx *BindContext, + parentTableDef *plan.TableDef, + parentAlias string, + affected []updateParentForeignKey, + lastNodeID int32, + selectNodeTag int32, + oldColName2Idx map[string]int32, + newColName2Idx map[string]int32, +) (int32, int32, error) { + parentObjRef, resolvedParent, err := builder.compCtx.ResolveById(parentTableDef.TblId, bindCtx.snapshot) + if err != nil { + return 0, 0, err + } + if resolvedParent == nil { + return 0, 0, moerr.NewInternalErrorf( + builder.GetContext(), "foreign-key parent table %d not found", parentTableDef.TblId) + } + + rowProject := getProjectionByLastNodeWithTag(builder, lastNodeID, selectNodeTag) + lockTag := builder.genNewBindTag() + lockProject := append([]*plan.Expr(nil), rowProject...) + lockTargets := make([]*plan.LockTarget, 0, len(affected)*2) + tableLocked := false + validIndexes, _ := getValidIndexes(parentTableDef) + + for _, affectedFK := range affected { + referencedNames, buildErr := updateParentColNames( + builder.GetContext(), parentTableDef, affectedFK.fk.ForeignCols) + if buildErr != nil { + return 0, 0, buildErr + } + + lockTableDef := parentTableDef + lockObjRef := parentObjRef + lockTable := false + var matchedIndex *plan.IndexDef + var pkeyNames []string + if parentTableDef.Pkey != nil { + pkeyNames = parentTableDef.Pkey.Names + if len(pkeyNames) == 0 && parentTableDef.Pkey.PkeyColName != "" { + pkeyNames = []string{parentTableDef.Pkey.PkeyColName} + } + } + if !updateForeignKeyPartsEqual(pkeyNames, referencedNames) { + for _, idxDef := range validIndexes { + if idxDef.Unique && updateForeignKeyPartsEqual(idxDef.Parts, referencedNames) { + matchedIndex = idxDef + break + } + } + if matchedIndex == nil { + lockTable = true + } else { + lockObjRef, lockTableDef, buildErr = builder.compCtx.ResolveIndexTableByRef( + parentObjRef, matchedIndex.IndexTableName, bindCtx.snapshot) + if buildErr != nil { + return 0, 0, buildErr + } + } + } + if lockTable && tableLocked { + continue + } + + for _, useNew := range []bool{false, true} { + keyParts := make([]*plan.Expr, len(referencedNames)) + for i, colName := range referencedNames { + pos := oldColName2Idx[parentAlias+"."+colName] + if useNew { + if newPos, ok := newColName2Idx[parentAlias+"."+colName]; ok { + pos = newPos + } + } + keyParts[i] = &plan.Expr{ + Typ: rowProject[pos].Typ, + Expr: &plan.Expr_Col{Col: &plan.ColRef{ + RelPos: selectNodeTag, ColPos: pos, + }}, + } + } + + lockExpr := keyParts[0] + if !lockTable { + if matchedIndex != nil { + prefixLengths, prefixErr := catalog.IndexPrefixLengthsFromParamsWithError( + matchedIndex.IndexAlgoParams) + if prefixErr != nil { + return 0, 0, prefixErr + } + for i := range keyParts { + keyParts[i], buildErr = builder.makeIndexPartExprFromInputExpr( + keyParts[i], referencedNames[i], prefixLengths) + if buildErr != nil { + return 0, 0, buildErr + } + } + } + if len(keyParts) > 1 || (matchedIndex != nil && indexTableStoresSerializedKey(matchedIndex)) { + lockExpr, buildErr = BindFuncExprImplByPlanExpr( + builder.GetContext(), "serial", keyParts) + if buildErr != nil { + return 0, 0, buildErr + } + } + } + lockProject = append(lockProject, lockExpr) + _, lockTyp := getPkPos(lockTableDef, false) + lockTargets = append(lockTargets, &plan.LockTarget{ + TableId: lockTableDef.TblId, ObjRef: lockObjRef, + PrimaryColIdxInBat: int32(len(lockProject) - 1), PrimaryColRelPos: lockTag, + PrimaryColTyp: lockTyp, Mode: lockpb.LockMode_Exclusive, LockTable: lockTable, + }) + if lockTable { + tableLocked = true + break + } + } + } + sort.SliceStable(lockTargets, func(i, j int) bool { + leftName, rightName := "", "" + if lockTargets[i].ObjRef != nil { + leftName = lockTargets[i].ObjRef.ObjName + } + if lockTargets[j].ObjRef != nil { + rightName = lockTargets[j].ObjRef.ObjName + } + if leftName != rightName { + return leftName < rightName + } + if lockTargets[i].TableId != lockTargets[j].TableId { + return lockTargets[i].TableId < lockTargets[j].TableId + } + return lockTargets[i].PrimaryColIdxInBat < lockTargets[j].PrimaryColIdxInBat + }) + + lockInputID := builder.appendNode(&plan.Node{ + NodeType: plan.Node_PROJECT, Children: []int32{lastNodeID}, + ProjectList: lockProject, BindingTags: []int32{lockTag}, + }, bindCtx) + lockOutput := getProjectionByLastNodeWithTag(builder, lockInputID, lockTag) + lockNodeID := builder.appendNode(&plan.Node{ + NodeType: plan.Node_LOCK_OP, Children: []int32{lockInputID}, + TableDef: resolvedParent, LockTargets: lockTargets, + }, bindCtx) + lockedNodeID := builder.appendNode(&plan.Node{ + NodeType: plan.Node_PROJECT, Children: []int32{lockNodeID}, + ProjectList: append([]*plan.Expr(nil), lockOutput[:len(rowProject)]...), + BindingTags: []int32{selectNodeTag}, + }, bindCtx) + lockedSinkID := appendSinkNodeWithTag(builder, bindCtx, lockedNodeID, selectNodeTag) + lockedStep := builder.appendStep(lockedSinkID) + return builder.appendTaggedSinkScan(bindCtx, lockedStep, selectNodeTag), selectNodeTag, nil +} + +func updateParentColNames( + ctx context.Context, + tableDef *plan.TableDef, + colIDs []uint64, +) ([]string, error) { + idToName := make(map[uint64]string, len(tableDef.Cols)) + for _, col := range tableDef.Cols { + idToName[col.ColId] = col.Name + } + names := make([]string, len(colIDs)) + for i, colID := range colIDs { + name, ok := idToName[colID] + if !ok { + return nil, moerr.NewInternalErrorf(ctx, "foreign-key parent column %d not found", colID) + } + names[i] = name + } + return names, nil +} + +func updateForeignKeyPartsEqual(left, right []string) bool { + if len(left) != len(right) { + return false + } + for i := range left { + if catalog.ResolveAlias(left[i]) != catalog.ResolveAlias(right[i]) { + return false + } + } + return true +} + func (builder *QueryBuilder) validateModernUpdateParentMutation( bindCtx *BindContext, parentTableDef *plan.TableDef, @@ -556,9 +765,13 @@ func (builder *QueryBuilder) validateModernUpdateParentRowClosure( } func (builder *QueryBuilder) newUnsupportedUpdateParentRowClosureError() error { - return moerr.NewNotSupported( - builder.GetContext(), - "parent foreign key action requires complete child update row closure", + return newUpdatePlannerRouteError( + updatePlannerRejected, + updateRouteReasonForeignKey, + moerr.NewNotSupported( + builder.GetContext(), + "parent foreign key action requires complete child update row closure", + ), ) } @@ -692,11 +905,9 @@ func (builder *QueryBuilder) appendUpdateParentMutation( tableDef *plan.TableDef tag int32 } - indexes := make([]mutationIndex, 0, len(childTableDef.Indexes)) - for _, idxDef := range childTableDef.Indexes { - if !catalog.IsRegularIndexAlgo(idxDef.IndexAlgo) { - continue - } + validIndexes, _ := getValidIndexes(childTableDef) + indexes := make([]mutationIndex, 0, len(validIndexes)) + for _, idxDef := range validIndexes { idxObjRef, idxTableDef, resolveErr := builder.compCtx.ResolveIndexTableByRef( affectedFK.childObjRef, idxDef.IndexTableName, @@ -1131,6 +1342,7 @@ func (builder *QueryBuilder) appendUpdateParentRestrictCheck( makePlan2StringConstExprWithType( "Cannot delete or update a parent row: a foreign key constraint fails", ), + makePlan2StringConstExprWithType(foreignKeyRowIsReferencedAssert), }, ) if err != nil { diff --git a/pkg/sql/plan/build.go b/pkg/sql/plan/build.go index fb4994e3122b6..4be93928f3e24 100644 --- a/pkg/sql/plan/build.go +++ b/pkg/sql/plan/build.go @@ -368,7 +368,7 @@ func bindAndOptimizeUpdateQuery(ctx CompilerContext, stmt *tree.Update, isPrepar if err != nil { return nil, err } - if enabled { + if enabled && query.HasForeignKeyAction { tblInfo, resolveErr := getUpdateTableInfo(ctx, stmt) if resolveErr != nil { return nil, resolveErr diff --git a/pkg/sql/plan/build_test.go b/pkg/sql/plan/build_test.go index 2e1801cc82580..fc3500d060886 100644 --- a/pkg/sql/plan/build_test.go +++ b/pkg/sql/plan/build_test.go @@ -2531,6 +2531,14 @@ func TestPreparedForeignKeyActionsMarkQueryUncacheable(t *testing.T) { require.True(t, query.GetHasForeignKeyAction()) }) + t.Run("unrelated child update remains cacheable", func(t *testing.T) { + mock := NewMockOptimizer(true) + setMockEmpDeptForeignKeyAction(t, mock, plan.ForeignKeyDef_SET_NULL, plan.ForeignKeyDef_CASCADE) + + query := buildPreparedQuery(t, mock, "prepare stmt1 from update emp set ename = ? where empno = ?") + require.False(t, query.GetHasForeignKeyAction()) + }) + t.Run("parent update cascade marks prepare uncacheable", func(t *testing.T) { mock := NewMockOptimizer(true) setMockEmpDeptForeignKeyAction(t, mock, plan.ForeignKeyDef_RESTRICT, plan.ForeignKeyDef_CASCADE) diff --git a/pkg/sql/plan/function/list_builtIn.go b/pkg/sql/plan/function/list_builtIn.go index 94ca8c889fac7..0a9be180f782a 100644 --- a/pkg/sql/plan/function/list_builtIn.go +++ b/pkg/sql/plan/function/list_builtIn.go @@ -14365,6 +14365,9 @@ var supportedOthersBuiltIns = []FuncNew{ if errType == "fk_no_referenced_row" { return moerr.NewErrFKNoReferencedRow2(proc.Ctx) } + if errType == "fk_row_is_referenced" { + return moerr.NewErrFKRowIsReferenced(proc.Ctx) + } return moerr.NewInternalError(proc.Ctx, errMsg) } res.AppendMustValue(true) diff --git a/pkg/sql/plan/update_planner_route_test.go b/pkg/sql/plan/update_planner_route_test.go index 608d2ea1550c7..e92b66b05f717 100644 --- a/pkg/sql/plan/update_planner_route_test.go +++ b/pkg/sql/plan/update_planner_route_test.go @@ -18,12 +18,14 @@ import ( "context" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/matrixorigin/matrixone/pkg/catalog" "github.com/matrixorigin/matrixone/pkg/common/moerr" "github.com/matrixorigin/matrixone/pkg/container/types" "github.com/matrixorigin/matrixone/pkg/defines" + lockpb "github.com/matrixorigin/matrixone/pkg/pb/lock" planpb "github.com/matrixorigin/matrixone/pkg/pb/plan" "github.com/matrixorigin/matrixone/pkg/sql/parsers" "github.com/matrixorigin/matrixone/pkg/sql/parsers/dialect" @@ -361,6 +363,10 @@ func TestBindUpdateForeignKeyRoutingByAffectedColumns(t *testing.T) { require.Equal(t, 1, countUpdateFkMarkJoins(query)) require.Equal(t, 1, countUpdateFkAsserts(query)) require.True(t, updateFkPlanContainsFunc(query, "<=>")) + require.True(t, updateFkPlanContainsTypedAssert( + query, + foreignKeyRowIsReferencedAssert, + )) }) t.Run("set default preserves restrict compatibility on modern path", func(t *testing.T) { @@ -472,8 +478,9 @@ func TestBindUpdateForeignKeyRoutingByAffectedColumns(t *testing.T) { builder := NewQueryBuilder(planpb.Query_UPDATE, mock.CurrentContext(), false, true) _, err = builder.bindUpdate(stmt.(*tree.Update), NewBindContext(builder, nil)) require.ErrorContains(t, err, "complete child update row closure") - route, _, _ := classifyUpdatePlannerError(err) + route, reason, _ := classifyUpdatePlannerError(err) require.Equal(t, updatePlannerRejected, route) + require.Equal(t, updateRouteReasonForeignKey, reason) }) } @@ -559,6 +566,107 @@ func TestBindUpdateForeignKeyRoutingByAffectedColumns(t *testing.T) { }) } +func TestUpdateParentForeignKeyLocksPrecedeChildConsumers(t *testing.T) { + mock := NewMockOptimizer(true) + setMockEmpDeptForeignKeyAction(t, mock, planpb.ForeignKeyDef_RESTRICT, planpb.ForeignKeyDef_CASCADE) + child := mock.ctxt.tables["emp"] + child.Indexes = append(child.Indexes, &planpb.IndexDef{ + IndexName: "building_idx", IndexTableName: "missing_building_idx", + Parts: []string{"ename"}, TableExist: false, + IndexAlgo: catalog.MoIndexDefaultAlgo.ToString(), + }) + + logicPlan, err := runOneStmt(mock, t, "update dept set deptno = deptno + 10 where deptno = 1") + require.NoError(t, err) + query := logicPlan.GetQuery() + parentID := mock.ctxt.tables["dept"].TblId + childID := child.TblId + + contains := func(root, target int32) bool { + var visit func(int32) bool + visit = func(nodeID int32) bool { + if nodeID == target { + return true + } + for _, childNodeID := range query.Nodes[nodeID].Children { + if visit(childNodeID) { + return true + } + } + return false + } + return visit(root) + } + stepContaining := func(target int32) int { + for step, root := range query.Steps { + if contains(root, target) { + return step + } + } + return -1 + } + var stepDependsOn func(int, int, map[int]bool) bool + stepDependsOn = func(step, dependency int, visited map[int]bool) bool { + if step == dependency { + return true + } + if visited[step] { + return false + } + visited[step] = true + var nodeDependsOn func(int32) bool + nodeDependsOn = func(nodeID int32) bool { + for _, sourceStep := range query.Nodes[nodeID].SourceStep { + if stepDependsOn(int(sourceStep), dependency, visited) { + return true + } + } + for _, childNodeID := range query.Nodes[nodeID].Children { + if nodeDependsOn(childNodeID) { + return true + } + } + return false + } + return nodeDependsOn(query.Steps[step]) + } + + lockNodeID := int32(-1) + for nodeID, node := range query.Nodes { + for _, target := range node.LockTargets { + if target.TableId == parentID && target.Mode == lockpb.LockMode_Exclusive { + lockNodeID = int32(nodeID) + require.Len(t, node.Children, 1) + lockInput := query.Nodes[node.Children[0]] + require.Less(t, int(target.PrimaryColIdxInBat), len(lockInput.ProjectList)) + assert.Equal(t, target.PrimaryColTyp.Id, + lockInput.ProjectList[target.PrimaryColIdxInBat].Typ.Id) + break + } + } + if lockNodeID >= 0 { + break + } + } + require.NotEqual(t, int32(-1), lockNodeID) + lockStep := stepContaining(lockNodeID) + require.GreaterOrEqual(t, lockStep, 0) + assert.Equal(t, planpb.Node_SINK, query.Nodes[query.Steps[lockStep]].NodeType) + + foundChildConsumer := false + for nodeID, node := range query.Nodes { + if node.NodeType != planpb.Node_TABLE_SCAN || node.TableDef == nil || node.TableDef.TblId != childID { + continue + } + foundChildConsumer = true + consumerStep := stepContaining(int32(nodeID)) + require.GreaterOrEqual(t, consumerStep, 0) + assert.True(t, stepDependsOn(consumerStep, lockStep, make(map[int]bool)), + "every child scan must depend on the materialized parent-key lock step") + } + assert.True(t, foundChildConsumer) +} + func TestBindUpdateSelfReferencingForeignKeyRouting(t *testing.T) { prepareSelfRef := func(mock *MockOptimizer) { tableDef := mock.ctxt.tables["self_ref"] diff --git a/test/distributed/cases/foreign_key/update_modern_fk.result b/test/distributed/cases/foreign_key/update_modern_fk.result index 81c68ed001e6f..381783c066084 100644 --- a/test/distributed/cases/foreign_key/update_modern_fk.result +++ b/test/distributed/cases/foreign_key/update_modern_fk.result @@ -105,7 +105,7 @@ insert into parent_restrict values (1), (2); insert into child_restrict values (10, 1); update parent_restrict set id = id where id = 1; update parent_restrict set id = 11 where id = 1; -internal error: Cannot delete or update a parent row: a foreign key constraint fails +Cannot delete or update a parent row: a foreign key constraint fails select * from parent_restrict order by id; ➤ id[4,32,0] 𝄀 1 𝄀 @@ -295,6 +295,31 @@ update update_preinsert_index set a = 90; Duplicate entry '90' for key 'a' set foreign_key_checks = 1; drop table update_preinsert_index; +create table parent_lock_order ( +id int primary key +); +create table child_lock_order ( +id int primary key, +parent_id int, +constraint fk_lock_order foreign key (parent_id) +references parent_lock_order(id) on update cascade +); +insert into parent_lock_order values (1); +begin; +update parent_lock_order set id = 2 where id = 1; +use update_modern_fk; +begin; +insert into child_lock_order values (10, 1); +internal error: Cannot add or update a child row: a foreign key constraint fails +commit; +commit; +select * from parent_lock_order; +➤ id[4,32,0] 𝄀 +2 +select * from child_lock_order; +➤ id[4,32,0] ¦ parent_id[4,32,0] +drop table child_lock_order; +drop table parent_lock_order; drop table child_generated_unique; drop table parent_generated_unique; drop table child_dual_fk; diff --git a/test/distributed/cases/foreign_key/update_modern_fk.sql b/test/distributed/cases/foreign_key/update_modern_fk.sql index c86768dbae605..3213e28aec732 100644 --- a/test/distributed/cases/foreign_key/update_modern_fk.sql +++ b/test/distributed/cases/foreign_key/update_modern_fk.sql @@ -234,6 +234,31 @@ update update_preinsert_index set a = 90; set foreign_key_checks = 1; drop table update_preinsert_index; +create table parent_lock_order ( + id int primary key +); +create table child_lock_order ( + id int primary key, + parent_id int, + constraint fk_lock_order foreign key (parent_id) + references parent_lock_order(id) on update cascade +); +insert into parent_lock_order values (1); +begin; +update parent_lock_order set id = 2 where id = 1; +-- @session:id=1{ +use update_modern_fk; +begin; +-- @wait:0:commit +insert into child_lock_order values (10, 1); +commit; +-- @session} +commit; +select * from parent_lock_order; +select * from child_lock_order; +drop table child_lock_order; +drop table parent_lock_order; + drop table child_generated_unique; drop table parent_generated_unique; drop table child_dual_fk; From be00085355c9573983d4c74b05307bd30d34e771 Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Sun, 2 Aug 2026 13:30:30 +0800 Subject: [PATCH 16/22] test: exercise concurrent parent FK locking --- test/distributed/cases/foreign_key/update_modern_fk.result | 6 ++++-- test/distributed/cases/foreign_key/update_modern_fk.sql | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/test/distributed/cases/foreign_key/update_modern_fk.result b/test/distributed/cases/foreign_key/update_modern_fk.result index 381783c066084..ea9f5c1aa783e 100644 --- a/test/distributed/cases/foreign_key/update_modern_fk.result +++ b/test/distributed/cases/foreign_key/update_modern_fk.result @@ -308,10 +308,12 @@ insert into parent_lock_order values (1); begin; update parent_lock_order set id = 2 where id = 1; use update_modern_fk; +set session lock_wait_timeout = 1; begin; insert into child_lock_order values (10, 1); -internal error: Cannot add or update a child row: a foreign key constraint fails -commit; +-- @regex("(?s)Lock wait timeout exceeded; try restarting transaction", true) +Lock wait timeout exceeded; try restarting transaction +rollback; commit; select * from parent_lock_order; ➤ id[4,32,0] 𝄀 diff --git a/test/distributed/cases/foreign_key/update_modern_fk.sql b/test/distributed/cases/foreign_key/update_modern_fk.sql index 3213e28aec732..516e6cab652d1 100644 --- a/test/distributed/cases/foreign_key/update_modern_fk.sql +++ b/test/distributed/cases/foreign_key/update_modern_fk.sql @@ -248,10 +248,11 @@ begin; update parent_lock_order set id = 2 where id = 1; -- @session:id=1{ use update_modern_fk; +set session lock_wait_timeout = 1; begin; --- @wait:0:commit +-- @regex("(?s)Lock wait timeout exceeded; try restarting transaction",true) insert into child_lock_order values (10, 1); -commit; +rollback; -- @session} commit; select * from parent_lock_order; From 1ed215c7623ca9381465796d4c4ef9080aa3aa23 Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Sun, 2 Aug 2026 18:15:00 +0800 Subject: [PATCH 17/22] test: update FK parent update error expectations --- .../cases/ddl/alter_table_rename_column.result | 2 +- test/distributed/cases/foreign_key/fk_base.result | 8 ++++---- .../cases/foreign_key/fk_self_refer2.result | 6 +++--- .../cases/foreign_key/fk_self_refer3.result | 4 ++-- test/distributed/cases/foreign_key/foreign_key.result | 10 +++++----- .../cases/foreign_key/foreign_key_multilayer.result | 10 +++++----- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/test/distributed/cases/ddl/alter_table_rename_column.result b/test/distributed/cases/ddl/alter_table_rename_column.result index 931218ed291c7..ed91770031001 100644 --- a/test/distributed/cases/ddl/alter_table_rename_column.result +++ b/test/distributed/cases/ddl/alter_table_rename_column.result @@ -242,7 +242,7 @@ delete from foreign02 where col2New = 2; update foreign01 set col2 = 'database ewueh ' where col1 = 1; internal error: column 'col2' not found in table update foreign01 set col1new = 9283923 where col1new = 1; -internal error: Cannot delete or update a parent row: a foreign key constraint fails +Cannot delete or update a parent row: a foreign key constraint fails select * from foreign01; col1new col2new col3 col4 1 sfhuwe 1 1 diff --git a/test/distributed/cases/foreign_key/fk_base.result b/test/distributed/cases/foreign_key/fk_base.result index f72503faecbb7..c7c4b0b2b6b88 100644 --- a/test/distributed/cases/foreign_key/fk_base.result +++ b/test/distributed/cases/foreign_key/fk_base.result @@ -51,7 +51,7 @@ create table c1 (a int, b int, foreign key f_a(a) references f1(a)); insert into f1 values (1,1), (2,2), (3,3); insert into c1 values (1,1), (2,2), (3,3); update f1 set a = 11 where b = 1; -internal error: Cannot delete or update a parent row: a foreign key constraint fails +Cannot delete or update a parent row: a foreign key constraint fails update c1 set a = 11 where b = 1; Cannot add or update a child row: a foreign key constraint fails update c1 set a = null where b = 1; @@ -120,7 +120,7 @@ INSERT INTO t_emp VALUES (7698,'BLAKE',30,2850.00); INSERT INTO t_emp VALUES (7782,'CLARK',10,2950.00); INSERT INTO t_emp VALUES (7788,'SCOTT',20,3500.00); update t_dept set id = 50 where name = 'ACCOUNTING'; -internal error: Cannot delete or update a parent row: a foreign key constraint fails +Cannot delete or update a parent row: a foreign key constraint fails delete from t_dept where name = 'ACCOUNTING'; internal error: Cannot delete or update a parent row: a foreign key constraint fails update t_emp set deptId = 50 where salary < 1500; @@ -329,7 +329,7 @@ update f1 set a=a; update f1 set a=1; update f1 set a=a, b=2; update f1 set a=2; -internal error: Cannot delete or update a parent row: a foreign key constraint fails +Cannot delete or update a parent row: a foreign key constraint fails drop table c1; drop table f1; create table f1(a int, b int, primary key (a,b)); @@ -340,7 +340,7 @@ update f1 set a=a; update f1 set a=a, b=1; update f1 set b=1, a=a; update f1 set a=a, b=2; -internal error: Cannot delete or update a parent row: a foreign key constraint fails +Cannot delete or update a parent row: a foreign key constraint fails drop table c1; drop table f1; drop database if exists db1; diff --git a/test/distributed/cases/foreign_key/fk_self_refer2.result b/test/distributed/cases/foreign_key/fk_self_refer2.result index 00347e527c3dc..0532ac764c153 100644 --- a/test/distributed/cases/foreign_key/fk_self_refer2.result +++ b/test/distributed/cases/foreign_key/fk_self_refer2.result @@ -35,7 +35,7 @@ internal error: Cannot delete or update a parent row: a foreign key constraint f delete from t1 where a = 3; delete from t1 where a = 8; update t1 set a = 3 where a = 2; -internal error: Cannot delete or update a parent row: a foreign key constraint fails +Cannot delete or update a parent row: a foreign key constraint fails update t1 set b = NULL where a = 2; delete from t1 where a= 2; update t1 set b = NULL where a = 1; @@ -85,7 +85,7 @@ internal error: Cannot delete or update a parent row: a foreign key constraint f delete from t1 where a = 3; delete from t1 where a = 8; update t1 set a = 3 where a = 2; -internal error: Cannot delete or update a parent row: a foreign key constraint fails +Cannot delete or update a parent row: a foreign key constraint fails update t1 set b = NULL where a = 2; update t1 set a = 3 where a = 2; delete from t1 where a = 2; @@ -145,7 +145,7 @@ internal error: Cannot delete or update a parent row: a foreign key constraint f update t1 set b = NULL where a = 1; Cannot add or update a child row: a foreign key constraint fails update t1 set b = 2 where a = 1; -internal error: Cannot delete or update a parent row: a foreign key constraint fails +Cannot delete or update a parent row: a foreign key constraint fails update t1 set c = NULL where a = 1; update t1 set b = 2 where a = 1; delete from t1 where a = 1; diff --git a/test/distributed/cases/foreign_key/fk_self_refer3.result b/test/distributed/cases/foreign_key/fk_self_refer3.result index 7369f60f7b940..00df469fe3c87 100644 --- a/test/distributed/cases/foreign_key/fk_self_refer3.result +++ b/test/distributed/cases/foreign_key/fk_self_refer3.result @@ -22,13 +22,13 @@ insert into t1 values (1,4,3,1,4,1,4); Cannot add or update a child row: a foreign key constraint fails insert into t1 values (1,4,3,1,4,1,3); update t1 set b = 5 where b = 4; -internal error: Cannot delete or update a parent row: a foreign key constraint fails +Cannot delete or update a parent row: a foreign key constraint fails update t1 set e = 5 where b = 4; Cannot add or update a child row: a foreign key constraint fails update t1 set e = NULL where b = 4; update t1 set b = 5 where b = 4; update t1 set c = 4 where b = 5; -internal error: Cannot delete or update a parent row: a foreign key constraint fails +Cannot delete or update a parent row: a foreign key constraint fails update t1 set g = 4 where b = 5; Cannot add or update a child row: a foreign key constraint fails update t1 set g = 2 where b = 5; diff --git a/test/distributed/cases/foreign_key/foreign_key.result b/test/distributed/cases/foreign_key/foreign_key.result index 5e33e550f155a..8de680e2665fd 100644 --- a/test/distributed/cases/foreign_key/foreign_key.result +++ b/test/distributed/cases/foreign_key/foreign_key.result @@ -21,7 +21,7 @@ internal error: Cannot add or update a child row: a foreign key constraint fails update fk_02 set col2='80' where col2='90'; Cannot add or update a child row: a foreign key constraint fails update fk_01 set col1=5 where col2=734; -internal error: Cannot delete or update a parent row: a foreign key constraint fails +Cannot delete or update a parent row: a foreign key constraint fails delete from fk_01 where col1='90'; internal error: Cannot delete or update a parent row: a foreign key constraint fails delete from fk_01 where col1='190'; @@ -143,9 +143,9 @@ Cannot add or update a child row: a foreign key constraint fails delete from fk_01 where col1=2 and col2='apple'; internal error: Cannot delete or update a parent row: a foreign key constraint fails update fk_01 set col1=3 where col1=2; -internal error: Cannot delete or update a parent row: a foreign key constraint fails +Cannot delete or update a parent row: a foreign key constraint fails update fk_01 set col1=1 where col1=2; -internal error: Cannot delete or update a parent row: a foreign key constraint fails +Cannot delete or update a parent row: a foreign key constraint fails select * from fk_01; col1 col2 col3 2 yellow 20 @@ -278,7 +278,7 @@ select * from fk_02; col1 col2 col3 col4 1 aa bb 2 update fk_01 set col1=8 where col1=2; -internal error: Cannot delete or update a parent row: a foreign key constraint fails +Cannot delete or update a parent row: a foreign key constraint fails select * from fk_01; col1 col2 col3 2 safer prow @@ -327,7 +327,7 @@ col1 col2 col3 3 ultra strong 4 aaa bbb update fk_01 set col1=8 where col1=2; -internal error: Cannot delete or update a parent row: a foreign key constraint fails +Cannot delete or update a parent row: a foreign key constraint fails update fk_02 set col4=5 where col3='ff'; select * from fk_01; col1 col2 col3 diff --git a/test/distributed/cases/foreign_key/foreign_key_multilayer.result b/test/distributed/cases/foreign_key/foreign_key_multilayer.result index 540762bfac51c..61639dab04804 100644 --- a/test/distributed/cases/foreign_key/foreign_key_multilayer.result +++ b/test/distributed/cases/foreign_key/foreign_key_multilayer.result @@ -90,21 +90,21 @@ L_COMMENT VARCHAR(44) NOT NULL, PRIMARY KEY (L_ORDERKEY, L_LINENUMBER),constraint fk_l1 foreign key(L_ORDERKEY) REFERENCES ORDERS_fk(o_orderkey),constraint fk_l2 foreign key(L_PARTKEY) REFERENCES PART_fk(P_PARTKEY),constraint fk_l3 foreign key(L_SUPPKEY) REFERENCES SUPPLIER_fk(S_SUPPKEY)); insert into lineitem_fk values(5999968,199,9991,2,46,63179.16,0.08,0.06,"R","F","1993-09-16","1993-09-21","1993-10-02","COLLECT COD","RAIL","dolites wake"); update nation_fk set n_nationkey=10 where n_nationkey=13; -internal error: Cannot delete or update a parent row: a foreign key constraint fails +Cannot delete or update a parent row: a foreign key constraint fails update lineitem_fk set l_partkey=2 where l_suppkey=9991; Cannot add or update a child row: a foreign key constraint fails update PARTSUPP_fk set PS_PARTKEY=40; Cannot add or update a child row: a foreign key constraint fails update orders_fk set O_ORDERKEY=1 where O_CUSTKEY=12; -internal error: Cannot delete or update a parent row: a foreign key constraint fails +Cannot delete or update a parent row: a foreign key constraint fails update supplier_fk set s_suppkey=11 where s_nationkey=13; -internal error: Cannot delete or update a parent row: a foreign key constraint fails +Cannot delete or update a parent row: a foreign key constraint fails update customer_fk set c_nationkey=6; Cannot add or update a child row: a foreign key constraint fails update part_fk set p_partkey=200 where P_RETAILPRICE=2097.99; -internal error: Cannot delete or update a parent row: a foreign key constraint fails +Cannot delete or update a parent row: a foreign key constraint fails update region_fk set r_regionkey=5 where r_name="ASIA"; -internal error: Cannot delete or update a parent row: a foreign key constraint fails +Cannot delete or update a parent row: a foreign key constraint fails delete from nation_fk where n_nationkey=13; internal error: Cannot delete or update a parent row: a foreign key constraint fails select * from SUPPLIER_fk; From b31e17634caed3e490462228e0b75102e0de4e4c Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Mon, 3 Aug 2026 11:30:28 +0800 Subject: [PATCH 18/22] fix(plan): close parent FK update safety gaps --- pkg/sql/compile/compile.go | 15 +- pkg/sql/compile/compile_test.go | 18 ++- pkg/sql/plan/bind_insert.go | 5 + pkg/sql/plan/bind_update.go | 16 ++- pkg/sql/plan/bind_update_fk.go | 128 ++++++++++++++---- pkg/sql/plan/build_dml_util.go | 16 +-- pkg/sql/plan/update_planner_route_test.go | 99 +++++++++++++- .../cases/foreign_key/update_modern_fk.result | 24 ++++ .../cases/foreign_key/update_modern_fk.sql | 19 +++ 9 files changed, 284 insertions(+), 56 deletions(-) diff --git a/pkg/sql/compile/compile.go b/pkg/sql/compile/compile.go index 07a3692630ecd..501f244b12a8b 100644 --- a/pkg/sql/compile/compile.go +++ b/pkg/sql/compile/compile.go @@ -652,11 +652,13 @@ func (c *Compile) runOnce() (err error) { // REPLACE parent checks and actions run before the main pipeline. query := c.pn.GetQuery() - if query != nil && query.StmtType == plan.Query_INSERT && len(query.GetDetectSqls()) != 0 { - if err = validateReplaceParentTxnMode( + if query != nil && len(query.GetDetectSqls()) != 0 { + if err = validateForeignKeyParentTxnMode( c.proc.Ctx, query, c.proc.GetTxnOperator().Txn().IsPessimistic()); err != nil { return err } + } + if query != nil && query.StmtType == plan.Query_INSERT && len(query.GetDetectSqls()) != 0 { for _, sql := range query.DetectSqls { if strings.HasPrefix(sql, "REPLACE_PARENT_PLAN:") { continue @@ -782,7 +784,8 @@ func (c *Compile) runOnce() (err error) { if strings.HasPrefix(sql, "REPLACE_PARENT_LOCK:") || strings.HasPrefix(sql, "REPLACE_PARENT_PLAN:") || strings.HasPrefix(sql, "REPLACE_PARENT_CHK:") || - strings.HasPrefix(sql, "REPLACE_PARENT_ACTION:") { + strings.HasPrefix(sql, "REPLACE_PARENT_ACTION:") || + strings.HasPrefix(sql, "UPDATE_PARENT_PLAN:") { continue } postCheckSqls = append(postCheckSqls, sql) @@ -799,7 +802,7 @@ func (c *Compile) runOnce() (err error) { return err } -func validateReplaceParentTxnMode(ctx context.Context, query *plan.Query, pessimistic bool) error { +func validateForeignKeyParentTxnMode(ctx context.Context, query *plan.Query, pessimistic bool) error { if pessimistic || query == nil { return nil } @@ -809,6 +812,10 @@ func validateReplaceParentTxnMode(ctx context.Context, query *plan.Query, pessim return moerr.NewNotSupported(ctx, "REPLACE on a referenced parent table in optimistic transaction mode") } + if strings.HasPrefix(sql, "UPDATE_PARENT_PLAN:") { + return moerr.NewNotSupported(ctx, + "UPDATE on a referenced parent table in optimistic transaction mode") + } } return nil } diff --git a/pkg/sql/compile/compile_test.go b/pkg/sql/compile/compile_test.go index 28ed80d2e58df..36cb5f24d6555 100644 --- a/pkg/sql/compile/compile_test.go +++ b/pkg/sql/compile/compile_test.go @@ -328,20 +328,24 @@ func TestConstructLockOpPreservesSharedTableMode(t *testing.T) { } } -func TestValidateReplaceParentTxnMode(t *testing.T) { +func TestValidateForeignKeyParentTxnMode(t *testing.T) { ctx := context.Background() query := &plan.Query{DetectSqls: []string{"REPLACE_PARENT_LOCK:select 1 for update"}} - require.NoError(t, validateReplaceParentTxnMode(ctx, query, true)) - require.ErrorContains(t, validateReplaceParentTxnMode(ctx, query, false), + require.NoError(t, validateForeignKeyParentTxnMode(ctx, query, true)) + require.ErrorContains(t, validateForeignKeyParentTxnMode(ctx, query, false), "optimistic transaction mode") query.DetectSqls = []string{"REPLACE_PARENT_PLAN:"} - require.NoError(t, validateReplaceParentTxnMode(ctx, query, true)) - require.ErrorContains(t, validateReplaceParentTxnMode(ctx, query, false), + require.NoError(t, validateForeignKeyParentTxnMode(ctx, query, true)) + require.ErrorContains(t, validateForeignKeyParentTxnMode(ctx, query, false), "optimistic transaction mode") - require.NoError(t, validateReplaceParentTxnMode(ctx, + query.DetectSqls = []string{"UPDATE_PARENT_PLAN:"} + require.NoError(t, validateForeignKeyParentTxnMode(ctx, query, true)) + require.ErrorContains(t, validateForeignKeyParentTxnMode(ctx, query, false), + "UPDATE on a referenced parent table") + require.NoError(t, validateForeignKeyParentTxnMode(ctx, &plan.Query{DetectSqls: []string{"select true"}}, false)) - require.NoError(t, validateReplaceParentTxnMode(ctx, nil, false)) + require.NoError(t, validateForeignKeyParentTxnMode(ctx, nil, false)) } func TestLockTableLocksAllPrePipelineTargets(t *testing.T) { diff --git a/pkg/sql/plan/bind_insert.go b/pkg/sql/plan/bind_insert.go index 2693301ff028a..e4c77ff3319d9 100644 --- a/pkg/sql/plan/bind_insert.go +++ b/pkg/sql/plan/bind_insert.go @@ -1304,6 +1304,11 @@ func (builder *QueryBuilder) appendModernChildFkMarkOks( PrimaryColTyp: fkLock.typ, Mode: lockpb.LockMode_Shared, LockTable: fkLock.lockTable, }) } + baseTableIDs := make(map[uint64]struct{}, len(nonSelfFks)) + for _, fk := range nonSelfFks { + baseTableIDs[fk.ForeignTbl] = struct{}{} + } + sortForeignKeyLockTargets(lockTargets, baseTableIDs) lockInputID := builder.appendNode(&plan.Node{ NodeType: plan.Node_PROJECT, Children: []int32{lastNodeID}, ProjectList: lockProject, BindingTags: []int32{lockTag}, diff --git a/pkg/sql/plan/bind_update.go b/pkg/sql/plan/bind_update.go index 3e7f4e5f16cf9..39e34da71ec6a 100644 --- a/pkg/sql/plan/bind_update.go +++ b/pkg/sql/plan/bind_update.go @@ -1219,13 +1219,25 @@ func (builder *QueryBuilder) updateActuallyAffectsForeignKey( if tableDef == nil || len(updatedCols) == 0 { return false, nil } + // Keep this sensitivity set aligned with the final UPDATE row image. The + // binder applies every ON UPDATE expression and recomputes generated columns + // after explicit assignments, so either can make an otherwise unrelated SET + // clause FK-sensitive. This must be computed before foreign_key_checks is + // consulted so prepared plans are rebuilt when that variable changes. + affectedCols := make(map[string]struct{}, len(updatedCols)+len(tableDef.Cols)) + for colName := range updatedCols { + affectedCols[colName] = struct{}{} + } colIDToName := make(map[uint64]string, len(tableDef.Cols)) for _, col := range tableDef.Cols { colIDToName[col.ColId] = col.Name + if col.OnUpdate != nil || col.GeneratedCol != nil { + affectedCols[col.Name] = struct{}{} + } } for _, fk := range tableDef.Fkeys { for _, colID := range fk.Cols { - if _, ok := updatedCols[colIDToName[colID]]; ok { + if _, ok := affectedCols[colIDToName[colID]]; ok { return true, nil } } @@ -1253,7 +1265,7 @@ func (builder *QueryBuilder) updateActuallyAffectsForeignKey( continue } for _, parentColID := range fk.ForeignCols { - if _, ok := updatedCols[colIDToName[parentColID]]; ok { + if _, ok := affectedCols[colIDToName[parentColID]]; ok { return true, nil } } diff --git a/pkg/sql/plan/bind_update_fk.go b/pkg/sql/plan/bind_update_fk.go index 6b04a8b1eab53..2aee85654ae22 100644 --- a/pkg/sql/plan/bind_update_fk.go +++ b/pkg/sql/plan/bind_update_fk.go @@ -292,6 +292,22 @@ func (builder *QueryBuilder) appendUpdateParentForeignKeyChecks( if len(affected) == 0 { return lastNodeID, selectNodeTag, nil } + // PREPARE may happen before the execution transaction exists. Keep a marker + // in the serialized plan so compile/run can enforce the actual transaction + // mode even when the binder could not observe it. + builder.qry.DetectSqls = append(builder.qry.DetectSqls, "UPDATE_PARENT_PLAN:") + if proc := builder.compCtx.GetProcess(); proc != nil { + if txnOp := proc.GetTxnOperator(); txnOp != nil && !txnOp.Txn().IsPessimistic() { + return 0, 0, newUpdatePlannerRouteError( + updatePlannerRejected, + updateRouteReasonForeignKey, + moerr.NewNotSupported( + builder.GetContext(), + "updating a referenced parent key in an optimistic transaction", + ), + ) + } + } lastNodeID, selectNodeTag, err := builder.appendUpdateParentKeyLocks( bindCtx, @@ -518,22 +534,7 @@ func (builder *QueryBuilder) appendUpdateParentKeyLocks( } } } - sort.SliceStable(lockTargets, func(i, j int) bool { - leftName, rightName := "", "" - if lockTargets[i].ObjRef != nil { - leftName = lockTargets[i].ObjRef.ObjName - } - if lockTargets[j].ObjRef != nil { - rightName = lockTargets[j].ObjRef.ObjName - } - if leftName != rightName { - return leftName < rightName - } - if lockTargets[i].TableId != lockTargets[j].TableId { - return lockTargets[i].TableId < lockTargets[j].TableId - } - return lockTargets[i].PrimaryColIdxInBat < lockTargets[j].PrimaryColIdxInBat - }) + sortForeignKeyLockTargets(lockTargets, map[uint64]struct{}{parentTableDef.TblId: {}}) lockInputID := builder.appendNode(&plan.Node{ NodeType: plan.Node_PROJECT, Children: []int32{lastNodeID}, @@ -586,6 +587,63 @@ func updateForeignKeyPartsEqual(left, right []string) bool { return true } +func sortForeignKeyLockTargets(lockTargets []*plan.LockTarget, baseTableIDs map[uint64]struct{}) { + sort.SliceStable(lockTargets, func(i, j int) bool { + _, leftBase := baseTableIDs[lockTargets[i].TableId] + _, rightBase := baseTableIDs[lockTargets[j].TableId] + if leftBase != rightBase { + return leftBase + } + leftName, rightName := "", "" + if lockTargets[i].ObjRef != nil { + leftName = lockTargets[i].ObjRef.ObjName + } + if lockTargets[j].ObjRef != nil { + rightName = lockTargets[j].ObjRef.ObjName + } + if leftName != rightName { + return leftName < rightName + } + if lockTargets[i].TableId != lockTargets[j].TableId { + return lockTargets[i].TableId < lockTargets[j].TableId + } + if lockTargets[i].LockTable != lockTargets[j].LockTable { + return !lockTargets[i].LockTable + } + if lockTargets[i].PrimaryColIdxInBat != lockTargets[j].PrimaryColIdxInBat { + return lockTargets[i].PrimaryColIdxInBat < lockTargets[j].PrimaryColIdxInBat + } + return lockTargets[i].PrimaryColRelPos < lockTargets[j].PrimaryColRelPos + }) +} + +func updateParentReferenceIsUnique( + ctx context.Context, + parentTableDef *plan.TableDef, + foreignCols []uint64, +) (bool, error) { + referencedNames, err := updateParentColNames(ctx, parentTableDef, foreignCols) + if err != nil { + return false, err + } + if parentTableDef.Pkey != nil { + pkeyNames := parentTableDef.Pkey.Names + if len(pkeyNames) == 0 && parentTableDef.Pkey.PkeyColName != "" { + pkeyNames = []string{parentTableDef.Pkey.PkeyColName} + } + if updateForeignKeyPartsEqual(pkeyNames, referencedNames) { + return true, nil + } + } + validIndexes, _ := getValidIndexes(parentTableDef) + for _, idxDef := range validIndexes { + if idxDef.Unique && updateForeignKeyPartsEqual(idxDef.Parts, referencedNames) { + return true, nil + } + } + return false, nil +} + func (builder *QueryBuilder) validateModernUpdateParentMutation( bindCtx *BindContext, parentTableDef *plan.TableDef, @@ -593,6 +651,21 @@ func (builder *QueryBuilder) validateModernUpdateParentMutation( ) error { childTableDef := affectedFK.childTableDef ensureName2ColIndexForReplace(childTableDef) + uniqueReference, err := updateParentReferenceIsUnique( + builder.GetContext(), parentTableDef, affectedFK.fk.ForeignCols) + if err != nil { + return err + } + if !uniqueReference { + return newUpdatePlannerRouteError( + updatePlannerRejected, + updateRouteReasonForeignKey, + moerr.NewNotSupported( + builder.GetContext(), + "parent foreign key action on non-unique referenced columns", + ), + ) + } parentColByID := make(map[uint64]*plan.ColDef, len(parentTableDef.Cols)) for _, col := range parentTableDef.Cols { parentColByID[col.ColId] = col @@ -908,6 +981,19 @@ func (builder *QueryBuilder) appendUpdateParentMutation( validIndexes, _ := getValidIndexes(childTableDef) indexes := make([]mutationIndex, 0, len(validIndexes)) for _, idxDef := range validIndexes { + indexAffected := false + for _, part := range idxDef.Parts { + colPos, ok := childTableDef.Name2ColIndex[catalog.ResolveAlias(part)] + if ok { + _, indexAffected = newChildExprs[colPos] + } + if indexAffected { + break + } + } + if !indexAffected { + continue + } idxObjRef, idxTableDef, resolveErr := builder.compCtx.ResolveIndexTableByRef( affectedFK.childObjRef, idxDef.IndexTableName, @@ -1096,15 +1182,7 @@ func (builder *QueryBuilder) appendUpdateParentMutation( ) } } - sort.SliceStable(lockTargets, func(i, j int) bool { - if lockTargets[i].TableId != lockTargets[j].TableId { - return lockTargets[i].TableId < lockTargets[j].TableId - } - if lockTargets[i].PrimaryColIdxInBat != lockTargets[j].PrimaryColIdxInBat { - return lockTargets[i].PrimaryColIdxInBat < lockTargets[j].PrimaryColIdxInBat - } - return lockTargets[i].PrimaryColRelPos < lockTargets[j].PrimaryColRelPos - }) + sortForeignKeyLockTargets(lockTargets, map[uint64]struct{}{childTableDef.TblId: {}}) actionNodeID = builder.appendNode(&plan.Node{ NodeType: plan.Node_LOCK_OP, Children: []int32{actionNodeID}, diff --git a/pkg/sql/plan/build_dml_util.go b/pkg/sql/plan/build_dml_util.go index 3831a4fecfddc..e6f6f07c88475 100644 --- a/pkg/sql/plan/build_dml_util.go +++ b/pkg/sql/plan/build_dml_util.go @@ -562,21 +562,7 @@ func appendRecursiveCascadeLockNode( } } - slices.SortStableFunc(lockTargets, func(left, right *plan.LockTarget) int { - if left.TableId < right.TableId { - return -1 - } - if left.TableId > right.TableId { - return 1 - } - if !left.LockTable && right.LockTable { - return -1 - } - if left.LockTable && !right.LockTable { - return 1 - } - return 0 - }) + sortForeignKeyLockTargets(lockTargets, map[uint64]struct{}{delCtx.tableDef.TblId: {}}) if len(lockProject) > len(rowProject) { sourceNodeID = builder.appendNode(&plan.Node{ NodeType: plan.Node_PROJECT, Children: []int32{sourceNodeID}, diff --git a/pkg/sql/plan/update_planner_route_test.go b/pkg/sql/plan/update_planner_route_test.go index e92b66b05f717..ae20df204c550 100644 --- a/pkg/sql/plan/update_planner_route_test.go +++ b/pkg/sql/plan/update_planner_route_test.go @@ -27,6 +27,7 @@ import ( "github.com/matrixorigin/matrixone/pkg/defines" lockpb "github.com/matrixorigin/matrixone/pkg/pb/lock" planpb "github.com/matrixorigin/matrixone/pkg/pb/plan" + txnpb "github.com/matrixorigin/matrixone/pkg/pb/txn" "github.com/matrixorigin/matrixone/pkg/sql/parsers" "github.com/matrixorigin/matrixone/pkg/sql/parsers/dialect" "github.com/matrixorigin/matrixone/pkg/sql/parsers/tree" @@ -401,15 +402,17 @@ func TestBindUpdateForeignKeyRoutingByAffectedColumns(t *testing.T) { require.Equal(t, 2, countUpdateFkPlanNodes(query, planpb.Node_MULTI_UPDATE)) require.True(t, query.HasForeignKeyAction) require.True(t, updateFkPlanContainsFunc(query, "<=>")) - hasIndexedChildAction := false + hasIndexedParentUpdate := false for _, node := range query.Nodes { if node.NodeType == planpb.Node_MULTI_UPDATE && len(node.UpdateCtxList) > 1 { - hasIndexedChildAction = true + hasIndexedParentUpdate = true } if node.NodeType != planpb.Node_MULTI_UPDATE { continue } if len(node.UpdateCtxList) > 0 && node.UpdateCtxList[0].ObjRef.ObjName == "emp" { + require.Len(t, node.UpdateCtxList, 1, + "cascade must not rewrite child indexes whose key parts are unchanged") for _, updateCtx := range node.UpdateCtxList { require.True(t, updateCtx.IgnoreAffectedRows) } @@ -420,7 +423,7 @@ func TestBindUpdateForeignKeyRoutingByAffectedColumns(t *testing.T) { } } } - require.True(t, hasIndexedChildAction) + require.True(t, hasIndexedParentUpdate) }) } @@ -566,6 +569,95 @@ func TestBindUpdateForeignKeyRoutingByAffectedColumns(t *testing.T) { }) } +func TestBindUpdateParentForeignKeySafetyGates(t *testing.T) { + prepareEmpDept := func(mock *MockOptimizer, action planpb.ForeignKeyDef_RefAction) { + setMockEmpDeptForeignKeyAction(t, mock, planpb.ForeignKeyDef_RESTRICT, action) + } + + for _, action := range []planpb.ForeignKeyDef_RefAction{ + planpb.ForeignKeyDef_RESTRICT, + planpb.ForeignKeyDef_CASCADE, + planpb.ForeignKeyDef_SET_NULL, + } { + t.Run("optimistic "+action.String(), func(t *testing.T) { + mock := NewMockOptimizer(true) + prepareEmpDept(mock, action) + setMockTxnMode(mock, txnpb.TxnMode_Optimistic) + + stmt, err := parsers.ParseOne( + mock.CurrentContext().GetContext(), dialect.MYSQL, + "UPDATE dept SET deptno = 2", 1) + require.NoError(t, err) + defer stmt.Free() + + builder := NewQueryBuilder(planpb.Query_UPDATE, mock.CurrentContext(), false, true) + _, err = builder.bindUpdate(stmt.(*tree.Update), NewBindContext(builder, nil)) + require.ErrorContains(t, err, "optimistic transaction") + route, reason, _ := classifyUpdatePlannerError(err) + require.Equal(t, updatePlannerRejected, route) + require.Equal(t, updateRouteReasonForeignKey, reason) + }) + } + + t.Run("non unique referenced prefix", func(t *testing.T) { + mock := NewMockOptimizer(true) + prepareEmpDept(mock, planpb.ForeignKeyDef_CASCADE) + dept := mock.ctxt.tables["dept"] + dept.Cols = append(dept.Cols, &planpb.ColDef{ + Name: catalog.CPrimaryKeyColName, ColId: 100, Hidden: true, + Typ: planpb.Type{Id: int32(types.T_varchar), Width: 65535}, + }) + ensureName2ColIndexForReplace(dept) + dept.Name2ColIndex[catalog.CPrimaryKeyColName] = int32(len(dept.Cols) - 1) + dept.Pkey = &planpb.PrimaryKeyDef{ + Names: []string{"deptno", "dname"}, PkeyColName: catalog.CPrimaryKeyColName, + } + + stmt, err := parsers.ParseOne( + mock.CurrentContext().GetContext(), dialect.MYSQL, + "UPDATE dept SET deptno = 2", 1) + require.NoError(t, err) + defer stmt.Free() + + builder := NewQueryBuilder(planpb.Query_UPDATE, mock.CurrentContext(), false, true) + _, err = builder.bindUpdate(stmt.(*tree.Update), NewBindContext(builder, nil)) + require.ErrorContains(t, err, "non-unique referenced columns") + route, reason, _ := classifyUpdatePlannerError(err) + require.Equal(t, updatePlannerRejected, route) + require.Equal(t, updateRouteReasonForeignKey, reason) + }) +} + +func TestBindUpdateForeignKeySensitivityIncludesImplicitFinalRowChanges(t *testing.T) { + for _, checksDisabled := range []bool{false, true} { + t.Run(map[bool]string{false: "checks enabled", true: "checks disabled"}[checksDisabled], func(t *testing.T) { + mock := NewMockOptimizer(true) + setMockEmpDeptForeignKeyAction( + t, mock, planpb.ForeignKeyDef_RESTRICT, planpb.ForeignKeyDef_RESTRICT) + setMockOnUpdateExpr(t, mock, "emp", "deptno", "2") + if checksDisabled { + mock.ctxt.SetContext(context.WithValue( + mock.ctxt.GetContext(), defines.DisableFkCheck{}, true)) + } + + logicPlan, err := runOneStmt(mock, t, "UPDATE emp SET sal = sal + 1") + require.NoError(t, err) + require.True(t, logicPlan.GetQuery().HasForeignKeyAction, + "implicit ON UPDATE FK column must make prepared plans cache-sensitive") + }) + } +} + +func TestSortForeignKeyLockTargetsUsesBaseBeforeHiddenTables(t *testing.T) { + base := &planpb.LockTarget{TableId: 20, ObjRef: &planpb.ObjectRef{ObjName: "z_parent"}} + hidden := &planpb.LockTarget{TableId: 10, ObjRef: &planpb.ObjectRef{ObjName: "__mo_index_a"}} + targets := []*planpb.LockTarget{hidden, base} + + sortForeignKeyLockTargets(targets, map[uint64]struct{}{base.TableId: {}}) + require.Same(t, base, targets[0]) + require.Same(t, hidden, targets[1]) +} + func TestUpdateParentForeignKeyLocksPrecedeChildConsumers(t *testing.T) { mock := NewMockOptimizer(true) setMockEmpDeptForeignKeyAction(t, mock, planpb.ForeignKeyDef_RESTRICT, planpb.ForeignKeyDef_CASCADE) @@ -579,6 +671,7 @@ func TestUpdateParentForeignKeyLocksPrecedeChildConsumers(t *testing.T) { logicPlan, err := runOneStmt(mock, t, "update dept set deptno = deptno + 10 where deptno = 1") require.NoError(t, err) query := logicPlan.GetQuery() + require.Contains(t, query.DetectSqls, "UPDATE_PARENT_PLAN:") parentID := mock.ctxt.tables["dept"].TblId childID := child.TblId diff --git a/test/distributed/cases/foreign_key/update_modern_fk.result b/test/distributed/cases/foreign_key/update_modern_fk.result index ea9f5c1aa783e..3c8a81face0fa 100644 --- a/test/distributed/cases/foreign_key/update_modern_fk.result +++ b/test/distributed/cases/foreign_key/update_modern_fk.result @@ -322,6 +322,30 @@ select * from child_lock_order; ➤ id[4,32,0] ¦ parent_id[4,32,0] drop table child_lock_order; drop table parent_lock_order; +create table parent_nonunique_prefix ( +a int, +b int, +primary key (a, b) +); +create table child_nonunique_prefix ( +id int primary key, +parent_a int, +constraint fk_nonunique_prefix foreign key (parent_a) +references parent_nonunique_prefix(a) on update cascade +); +insert into parent_nonunique_prefix values (1, 1), (1, 2); +insert into child_nonunique_prefix values (10, 1); +update parent_nonunique_prefix set a = b + 1 where a = 1; +not supported: parent foreign key action on non-unique referenced columns +select * from parent_nonunique_prefix order by a, b; +➤ a[4,32,0] ¦ b[4,32,0] 𝄀 +1 ¦ 1 𝄀 +1 ¦ 2 +select * from child_nonunique_prefix order by id; +➤ id[4,32,0] ¦ parent_a[4,32,0] 𝄀 +10 ¦ 1 +drop table child_nonunique_prefix; +drop table parent_nonunique_prefix; drop table child_generated_unique; drop table parent_generated_unique; drop table child_dual_fk; diff --git a/test/distributed/cases/foreign_key/update_modern_fk.sql b/test/distributed/cases/foreign_key/update_modern_fk.sql index 516e6cab652d1..20c62363523c6 100644 --- a/test/distributed/cases/foreign_key/update_modern_fk.sql +++ b/test/distributed/cases/foreign_key/update_modern_fk.sql @@ -260,6 +260,25 @@ select * from child_lock_order; drop table child_lock_order; drop table parent_lock_order; +create table parent_nonunique_prefix ( + a int, + b int, + primary key (a, b) +); +create table child_nonunique_prefix ( + id int primary key, + parent_a int, + constraint fk_nonunique_prefix foreign key (parent_a) + references parent_nonunique_prefix(a) on update cascade +); +insert into parent_nonunique_prefix values (1, 1), (1, 2); +insert into child_nonunique_prefix values (10, 1); +update parent_nonunique_prefix set a = b + 1 where a = 1; +select * from parent_nonunique_prefix order by a, b; +select * from child_nonunique_prefix order by id; +drop table child_nonunique_prefix; +drop table parent_nonunique_prefix; + drop table child_generated_unique; drop table parent_generated_unique; drop table child_dual_fk; From 29b06e9c9598d93ef16bad3d485323b3282f6132 Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Mon, 3 Aug 2026 12:21:45 +0800 Subject: [PATCH 19/22] fix(plan): preserve non-unique FK cascade compatibility --- pkg/sql/plan/bind_update.go | 30 +++++++++- pkg/sql/plan/bind_update_fk.go | 55 ++++++++++++------- pkg/sql/plan/function/list_builtIn.go | 3 + pkg/sql/plan/update_planner_route_test.go | 17 ++---- .../cases/foreign_key/update_modern_fk.result | 2 +- 5 files changed, 72 insertions(+), 35 deletions(-) diff --git a/pkg/sql/plan/bind_update.go b/pkg/sql/plan/bind_update.go index 39e34da71ec6a..4501caefd3599 100644 --- a/pkg/sql/plan/bind_update.go +++ b/pkg/sql/plan/bind_update.go @@ -1454,6 +1454,19 @@ func (builder *QueryBuilder) appendRowNumberDedupNode( selectNode *plan.Node, selectNodeTag int32, partitionColPositions []int32, +) (int32, *plan.Node, int32, error) { + return builder.appendRowNumberGuardNode( + bindCtx, lastNodeID, selectNode, selectNodeTag, partitionColPositions, "", "") +} + +func (builder *QueryBuilder) appendRowNumberGuardNode( + bindCtx *BindContext, + lastNodeID int32, + selectNode *plan.Node, + selectNodeTag int32, + partitionColPositions []int32, + duplicateErrorMessage string, + duplicateErrorType string, ) (int32, *plan.Node, int32, error) { partitionByExprs := make([]*plan.Expr, 0, len(partitionColPositions)) childColExpr := func(pos int32) *plan.Expr { @@ -1567,10 +1580,25 @@ func (builder *QueryBuilder) appendRowNumberDedupNode( if err != nil { return 0, nil, 0, err } + guardExpr := keepFirstRowExpr + if duplicateErrorType != "" { + guardExpr, err = BindFuncExprImplByPlanExpr( + builder.GetContext(), + "assert", + []*plan.Expr{ + keepFirstRowExpr, + makePlan2StringConstExprWithType(duplicateErrorMessage), + makePlan2StringConstExprWithType(duplicateErrorType), + }, + ) + if err != nil { + return 0, nil, 0, err + } + } lastNodeID = builder.appendNode(&plan.Node{ NodeType: plan.Node_FILTER, Children: []int32{lastNodeID}, - FilterList: []*plan.Expr{keepFirstRowExpr}, + FilterList: []*plan.Expr{guardExpr}, }, bindCtx) projectList := make([]*plan.Expr, len(selectNode.ProjectList)) diff --git a/pkg/sql/plan/bind_update_fk.go b/pkg/sql/plan/bind_update_fk.go index 2aee85654ae22..493e992d5180e 100644 --- a/pkg/sql/plan/bind_update_fk.go +++ b/pkg/sql/plan/bind_update_fk.go @@ -27,8 +27,9 @@ import ( ) const ( - foreignKeyNoReferencedRowAssert = "fk_no_referenced_row" - foreignKeyRowIsReferencedAssert = "fk_row_is_referenced" + foreignKeyNoReferencedRowAssert = "fk_no_referenced_row" + foreignKeyRowIsReferencedAssert = "fk_row_is_referenced" + foreignKeyAmbiguousMappingAssert = "fk_ambiguous_parent_mapping" ) func (builder *QueryBuilder) updateInputProjectNode(nodeID int32) *plan.Node { @@ -651,21 +652,6 @@ func (builder *QueryBuilder) validateModernUpdateParentMutation( ) error { childTableDef := affectedFK.childTableDef ensureName2ColIndexForReplace(childTableDef) - uniqueReference, err := updateParentReferenceIsUnique( - builder.GetContext(), parentTableDef, affectedFK.fk.ForeignCols) - if err != nil { - return err - } - if !uniqueReference { - return newUpdatePlannerRouteError( - updatePlannerRejected, - updateRouteReasonForeignKey, - moerr.NewNotSupported( - builder.GetContext(), - "parent foreign key action on non-unique referenced columns", - ), - ) - } parentColByID := make(map[uint64]*plan.ColDef, len(parentTableDef.Cols)) for _, col := range parentTableDef.Cols { parentColByID[col.ColId] = col @@ -887,6 +873,37 @@ func (builder *QueryBuilder) appendUpdateParentMutation( FilterList: []*plan.Expr{changed}, }, bindCtx) } + parentColIDToName := make(map[uint64]string, len(parentTableDef.Cols)) + for _, col := range parentTableDef.Cols { + parentColIDToName[col.ColId] = col.Name + } + + uniqueReference, err := updateParentReferenceIsUnique( + builder.GetContext(), parentTableDef, affectedFK.fk.ForeignCols) + if err != nil { + return err + } + if !uniqueReference { + partitionPositions := make([]int32, 0, len(affectedFK.fk.ForeignCols)) + for _, parentColID := range affectedFK.fk.ForeignCols { + partitionPositions = append( + partitionPositions, + oldColName2Idx[parentAlias+"."+parentColIDToName[parentColID]], + ) + } + parentNodeID, parentNode, sourceTag, err = builder.appendRowNumberGuardNode( + bindCtx, + parentNodeID, + parentNode, + sourceTag, + partitionPositions, + "parent foreign key action has an ambiguous non-unique referenced-key mapping", + foreignKeyAmbiguousMappingAssert, + ) + if err != nil { + return err + } + } childTableDef := affectedFK.childTableDef childTag := builder.genNewBindTag() @@ -899,10 +916,6 @@ func (builder *QueryBuilder) appendUpdateParentMutation( ScanSnapshot: bindCtx.snapshot, }, bindCtx) - parentColIDToName := make(map[uint64]string, len(parentTableDef.Cols)) - for _, col := range parentTableDef.Cols { - parentColIDToName[col.ColId] = col.Name - } childColIDToPos := make(map[uint64]int32, len(childTableDef.Cols)) for i, col := range childTableDef.Cols { childColIDToPos[col.ColId] = int32(i) diff --git a/pkg/sql/plan/function/list_builtIn.go b/pkg/sql/plan/function/list_builtIn.go index 0a9be180f782a..acb61fc7f236b 100644 --- a/pkg/sql/plan/function/list_builtIn.go +++ b/pkg/sql/plan/function/list_builtIn.go @@ -14368,6 +14368,9 @@ var supportedOthersBuiltIns = []FuncNew{ if errType == "fk_row_is_referenced" { return moerr.NewErrFKRowIsReferenced(proc.Ctx) } + if errType == "fk_ambiguous_parent_mapping" { + return moerr.NewNotSupported(proc.Ctx, errMsg) + } return moerr.NewInternalError(proc.Ctx, errMsg) } res.AppendMustValue(true) diff --git a/pkg/sql/plan/update_planner_route_test.go b/pkg/sql/plan/update_planner_route_test.go index ae20df204c550..a8e5c24268b35 100644 --- a/pkg/sql/plan/update_planner_route_test.go +++ b/pkg/sql/plan/update_planner_route_test.go @@ -599,18 +599,12 @@ func TestBindUpdateParentForeignKeySafetyGates(t *testing.T) { }) } - t.Run("non unique referenced prefix", func(t *testing.T) { + t.Run("non unique referenced prefix uses runtime ambiguity guard", func(t *testing.T) { mock := NewMockOptimizer(true) prepareEmpDept(mock, planpb.ForeignKeyDef_CASCADE) dept := mock.ctxt.tables["dept"] - dept.Cols = append(dept.Cols, &planpb.ColDef{ - Name: catalog.CPrimaryKeyColName, ColId: 100, Hidden: true, - Typ: planpb.Type{Id: int32(types.T_varchar), Width: 65535}, - }) - ensureName2ColIndexForReplace(dept) - dept.Name2ColIndex[catalog.CPrimaryKeyColName] = int32(len(dept.Cols) - 1) dept.Pkey = &planpb.PrimaryKeyDef{ - Names: []string{"deptno", "dname"}, PkeyColName: catalog.CPrimaryKeyColName, + Names: []string{"deptno", "dname"}, PkeyColName: "deptno", } stmt, err := parsers.ParseOne( @@ -621,10 +615,9 @@ func TestBindUpdateParentForeignKeySafetyGates(t *testing.T) { builder := NewQueryBuilder(planpb.Query_UPDATE, mock.CurrentContext(), false, true) _, err = builder.bindUpdate(stmt.(*tree.Update), NewBindContext(builder, nil)) - require.ErrorContains(t, err, "non-unique referenced columns") - route, reason, _ := classifyUpdatePlannerError(err) - require.Equal(t, updatePlannerRejected, route) - require.Equal(t, updateRouteReasonForeignKey, reason) + require.NoError(t, err) + require.True(t, updateFkPlanContainsTypedAssert( + builder.qry, foreignKeyAmbiguousMappingAssert)) }) } diff --git a/test/distributed/cases/foreign_key/update_modern_fk.result b/test/distributed/cases/foreign_key/update_modern_fk.result index 3c8a81face0fa..410bf2f27a654 100644 --- a/test/distributed/cases/foreign_key/update_modern_fk.result +++ b/test/distributed/cases/foreign_key/update_modern_fk.result @@ -336,7 +336,7 @@ references parent_nonunique_prefix(a) on update cascade insert into parent_nonunique_prefix values (1, 1), (1, 2); insert into child_nonunique_prefix values (10, 1); update parent_nonunique_prefix set a = b + 1 where a = 1; -not supported: parent foreign key action on non-unique referenced columns +not supported: parent foreign key action has an ambiguous non-unique referenced-key mapping select * from parent_nonunique_prefix order by a, b; ➤ a[4,32,0] ¦ b[4,32,0] 𝄀 1 ¦ 1 𝄀 From 956a8cedf314ab2b100bf35ce26e2d752cf53a32 Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Mon, 3 Aug 2026 14:24:24 +0800 Subject: [PATCH 20/22] test(plan): cover FK execution safety boundaries --- pkg/tests/issues/issue_26339_test.go | 183 +++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 pkg/tests/issues/issue_26339_test.go diff --git a/pkg/tests/issues/issue_26339_test.go b/pkg/tests/issues/issue_26339_test.go new file mode 100644 index 0000000000000..441e91e1f8a78 --- /dev/null +++ b/pkg/tests/issues/issue_26339_test.go @@ -0,0 +1,183 @@ +// Copyright 2026 Matrix Origin +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package issues + +import ( + "context" + "database/sql" + "fmt" + "strings" + "testing" + "time" + + "github.com/go-sql-driver/mysql" + moruntime "github.com/matrixorigin/matrixone/pkg/common/runtime" + "github.com/matrixorigin/matrixone/pkg/embed" + pbtxn "github.com/matrixorigin/matrixone/pkg/pb/txn" + "github.com/stretchr/testify/require" +) + +func TestIssue26339ForeignKeyExecutionRegressions(t *testing.T) { + runAuthenticatedClusterTest(t, func(c embed.Cluster) { + ctx, cancel := context.WithTimeout(context.Background(), 240*time.Second) + defer cancel() + + cn, err := c.GetCNService(0) + require.NoError(t, err) + port := cn.GetServiceConfig().CN.Frontend.Port + db, err := sql.Open("mysql", fmt.Sprintf("dump:111@tcp(127.0.0.1:%d)/", port)) + require.NoError(t, err) + defer db.Close() + db.SetMaxOpenConns(3) + + const database = "issue_26339_fk_execution" + defer func() { + cleanupCtx, cleanupCancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cleanupCancel() + execSQLMaybe(t, cleanupCtx, db, "drop database if exists "+database) + }() + execSQLRequire(t, ctx, db, "drop database if exists "+database) + execSQLRequire(t, ctx, db, "create database "+database) + + t.Run("optimistic parent update is rejected before concurrent child write", func(t *testing.T) { + execSQLRequire(t, ctx, db, "create table "+database+".optimistic_parent (id int primary key)") + execSQLRequire(t, ctx, db, "create table "+database+".optimistic_child ("+ + "id int primary key, parent_id int, foreign key (parent_id) "+ + "references "+database+".optimistic_parent(id) on update cascade)") + execSQLRequire(t, ctx, db, "insert into "+database+".optimistic_parent values (1)") + + rt := moruntime.ServiceRuntime(cn.ServiceID()) + oldMode, hadMode := rt.GetGlobalVariables(moruntime.TxnMode) + oldIsolation, hadIsolation := rt.GetGlobalVariables(moruntime.TxnIsolation) + rt.SetGlobalVariables(moruntime.TxnMode, pbtxn.TxnMode_Optimistic) + rt.SetGlobalVariables(moruntime.TxnIsolation, pbtxn.TxnIsolation_SI) + defer func() { + if hadMode { + rt.SetGlobalVariables(moruntime.TxnMode, oldMode) + } else { + rt.SetGlobalVariables(moruntime.TxnMode, pbtxn.TxnMode_Pessimistic) + } + if hadIsolation { + rt.SetGlobalVariables(moruntime.TxnIsolation, oldIsolation) + } else { + rt.SetGlobalVariables(moruntime.TxnIsolation, pbtxn.TxnIsolation_RC) + } + }() + + parentConn, err := db.Conn(ctx) + require.NoError(t, err) + defer parentConn.Close() + childConn, err := db.Conn(ctx) + require.NoError(t, err) + defer childConn.Close() + + _, err = parentConn.ExecContext(ctx, "begin") + require.NoError(t, err) + defer func() { + rollbackCtx, rollbackCancel := context.WithTimeout(context.Background(), 10*time.Second) + defer rollbackCancel() + _, _ = parentConn.ExecContext(rollbackCtx, "rollback") + }() + _, err = childConn.ExecContext(ctx, "begin") + require.NoError(t, err) + defer func() { + rollbackCtx, rollbackCancel := context.WithTimeout(context.Background(), 10*time.Second) + defer rollbackCancel() + _, _ = childConn.ExecContext(rollbackCtx, "rollback") + }() + + _, err = parentConn.ExecContext(ctx, + "update "+database+".optimistic_parent set id = 2 where id = 1") + require.Error(t, err) + var mysqlErr *mysql.MySQLError + require.ErrorAs(t, err, &mysqlErr) + require.Equal(t, uint16(20105), mysqlErr.Number) + require.Contains(t, err.Error(), "optimistic transaction") + + _, err = childConn.ExecContext(ctx, + "insert into "+database+".optimistic_child values (10, 1)") + require.NoError(t, err) + _, err = childConn.ExecContext(ctx, "commit") + require.NoError(t, err) + _, err = parentConn.ExecContext(ctx, "rollback") + require.NoError(t, err) + + var parentID, childParentID, orphanCount int + require.NoError(t, db.QueryRowContext(ctx, + "select id from "+database+".optimistic_parent").Scan(&parentID)) + require.NoError(t, db.QueryRowContext(ctx, + "select parent_id from "+database+".optimistic_child where id = 10").Scan(&childParentID)) + require.NoError(t, db.QueryRowContext(ctx, + "select count(*) from "+database+".optimistic_child c left join "+ + database+".optimistic_parent p on c.parent_id = p.id where p.id is null").Scan(&orphanCount)) + require.Equal(t, 1, parentID) + require.Equal(t, 1, childParentID) + require.Zero(t, orphanCount) + }) + + t.Run("prepared implicit on update observes enabled foreign key checks", func(t *testing.T) { + conn, err := db.Conn(ctx) + require.NoError(t, err) + defer conn.Close() + + execConn := func(statement string) error { + _, execErr := conn.ExecContext(ctx, statement) + return execErr + } + require.NoError(t, execConn("create table "+database+".prepared_parent (id datetime primary key)")) + require.NoError(t, execConn("create table "+database+".prepared_child ("+ + "id int primary key, parent_id datetime on update current_timestamp, note int, "+ + "foreign key (parent_id) references "+database+".prepared_parent(id))")) + require.NoError(t, execConn( + "insert into "+database+".prepared_parent values ('2000-01-01 00:00:00')")) + require.NoError(t, execConn( + "insert into "+database+".prepared_child values (10, '2000-01-01 00:00:00', 1)")) + + require.NoError(t, execConn("set foreign_key_checks = 0")) + require.NoError(t, execConn("prepare stmt_26339 from 'update "+database+ + ".prepared_child set note = 2 where id = 10'")) + defer func() { + deallocateCtx, deallocateCancel := context.WithTimeout(context.Background(), 10*time.Second) + defer deallocateCancel() + _, _ = conn.ExecContext(deallocateCtx, "deallocate prepare stmt_26339") + }() + require.NoError(t, execConn("set foreign_key_checks = 1")) + executeErr := execConn("execute stmt_26339") + require.Error(t, executeErr) + var mysqlErr *mysql.MySQLError + require.ErrorAs(t, executeErr, &mysqlErr) + require.Equal(t, uint16(1452), mysqlErr.Number) + require.True(t, + strings.Contains(executeErr.Error(), "Cannot add or update a child row") || + strings.Contains(executeErr.Error(), "foreign key constraint fails"), + "expected a foreign-key violation, got %v", executeErr) + + var parentID, childParentID string + var note, orphanCount int + require.NoError(t, conn.QueryRowContext(ctx, + "select cast(id as char) from "+database+".prepared_parent").Scan(&parentID)) + require.NoError(t, conn.QueryRowContext(ctx, + "select cast(parent_id as char), note from "+database+".prepared_child where id = 10"). + Scan(&childParentID, ¬e)) + require.NoError(t, conn.QueryRowContext(ctx, + "select count(*) from "+database+".prepared_child c left join "+ + database+".prepared_parent p on c.parent_id = p.id where p.id is null").Scan(&orphanCount)) + require.Equal(t, "2000-01-01 00:00:00", parentID) + require.Equal(t, parentID, childParentID) + require.Equal(t, 1, note) + require.Zero(t, orphanCount) + }) + }) +} From ed912c99e995dfe41320ac5ad9b8899f9ece5e1d Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Mon, 3 Aug 2026 15:55:17 +0800 Subject: [PATCH 21/22] fix(plan): validate non-unique FK child mappings --- pkg/sql/plan/bind_update_fk.go | 178 +++++++++++++++++++--- pkg/sql/plan/update_planner_route_test.go | 53 ++++--- pkg/tests/issues/issue_26339_test.go | 78 ++++++++++ 3 files changed, 269 insertions(+), 40 deletions(-) diff --git a/pkg/sql/plan/bind_update_fk.go b/pkg/sql/plan/bind_update_fk.go index 493e992d5180e..d15d6382998de 100644 --- a/pkg/sql/plan/bind_update_fk.go +++ b/pkg/sql/plan/bind_update_fk.go @@ -883,28 +883,6 @@ func (builder *QueryBuilder) appendUpdateParentMutation( if err != nil { return err } - if !uniqueReference { - partitionPositions := make([]int32, 0, len(affectedFK.fk.ForeignCols)) - for _, parentColID := range affectedFK.fk.ForeignCols { - partitionPositions = append( - partitionPositions, - oldColName2Idx[parentAlias+"."+parentColIDToName[parentColID]], - ) - } - parentNodeID, parentNode, sourceTag, err = builder.appendRowNumberGuardNode( - bindCtx, - parentNodeID, - parentNode, - sourceTag, - partitionPositions, - "parent foreign key action has an ambiguous non-unique referenced-key mapping", - foreignKeyAmbiguousMappingAssert, - ) - if err != nil { - return err - } - } - childTableDef := affectedFK.childTableDef childTag := builder.genNewBindTag() builder.addNameByColRef(childTag, childTableDef) @@ -984,6 +962,72 @@ func (builder *QueryBuilder) appendUpdateParentMutation( JoinType: plan.Node_INNER, OnList: joinPreds, }, bindCtx) + if !uniqueReference { + mappingTag := builder.genNewBindTag() + mappingProjection := make([]*plan.Expr, 0, len(childTableDef.Cols)+len(newChildExprs)) + for i, col := range childTableDef.Cols { + mappingProjection = append(mappingProjection, &plan.Expr{ + Typ: col.Typ, + Expr: &plan.Expr_Col{Col: &plan.ColRef{ + RelPos: childTag, + ColPos: int32(i), + }}, + }) + } + mappingNewChildExprs := make(map[int32]*plan.Expr, len(newChildExprs)) + for _, childColID := range affectedFK.fk.Cols { + childPos := childColIDToPos[childColID] + newPos := int32(len(mappingProjection)) + mappingProjection = append(mappingProjection, newChildExprs[childPos]) + mappingNewChildExprs[childPos] = &plan.Expr{ + Typ: childTableDef.Cols[childPos].Typ, + Expr: &plan.Expr_Col{Col: &plan.ColRef{ + RelPos: mappingTag, + ColPos: newPos, + }}, + } + } + joinNodeID = builder.appendNode(&plan.Node{ + NodeType: plan.Node_PROJECT, + Children: []int32{joinNodeID}, + ProjectList: mappingProjection, + BindingTags: []int32{mappingTag}, + }, bindCtx) + childTag = mappingTag + newChildExprs = mappingNewChildExprs + + childRowIDPos := childTableDef.Name2ColIndex[catalog.Row_ID] + childRowIDExpr := &plan.Expr{ + Typ: childTableDef.Cols[childRowIDPos].Typ, + Expr: &plan.Expr_Col{Col: &plan.ColRef{ + RelPos: childTag, + ColPos: childRowIDPos, + }}, + } + mappingIdentity := []*plan.Expr{childRowIDExpr} + if affectedFK.fk.OnUpdate == plan.ForeignKeyDef_CASCADE { + for _, childColID := range affectedFK.fk.Cols { + mappingIdentity = append(mappingIdentity, newChildExprs[childColIDToPos[childColID]]) + } + } + joinNodeID, err = builder.appendRowNumberMappingGuardNode( + bindCtx, joinNodeID, mappingIdentity, "", "") + if err != nil { + return err + } + if affectedFK.fk.OnUpdate == plan.ForeignKeyDef_CASCADE { + joinNodeID, err = builder.appendRowNumberMappingGuardNode( + bindCtx, + joinNodeID, + []*plan.Expr{childRowIDExpr}, + "parent foreign key action has an ambiguous non-unique referenced-key mapping", + foreignKeyAmbiguousMappingAssert, + ) + if err != nil { + return err + } + } + } type mutationIndex struct { def *plan.IndexDef @@ -1214,6 +1258,96 @@ func (builder *QueryBuilder) appendUpdateParentMutation( return nil } +func (builder *QueryBuilder) appendRowNumberMappingGuardNode( + bindCtx *BindContext, + lastNodeID int32, + partitionByExprs []*plan.Expr, + duplicateErrorMessage string, + duplicateErrorType string, +) (int32, error) { + if len(partitionByExprs) == 0 { + return lastNodeID, nil + } + + partitionBy := make([]*plan.OrderBySpec, 0, len(partitionByExprs)) + for _, expr := range partitionByExprs { + partitionBy = append(partitionBy, &plan.OrderBySpec{ + Expr: expr, + Flag: plan.OrderBySpec_INTERNAL, + }) + } + lastNodeID = builder.appendNode(&plan.Node{ + NodeType: plan.Node_PARTITION, + Children: []int32{lastNodeID}, + OrderBy: partitionBy, + }, bindCtx) + + rowNumberFunc, err := BindFuncExprImplByPlanExpr(builder.GetContext(), "row_number", nil) + if err != nil { + return 0, err + } + windowTag := builder.genNewBindTag() + lastNodeID = builder.appendNode(&plan.Node{ + NodeType: plan.Node_WINDOW, + Children: []int32{lastNodeID}, + WinSpecList: []*plan.Expr{{ + Typ: rowNumberFunc.Typ, + Expr: &plan.Expr_W{W: &plan.WindowSpec{ + WindowFunc: rowNumberFunc, + Name: "row_number", + PartitionBy: partitionByExprs, + Frame: &plan.FrameClause{ + Type: plan.FrameClause_ROWS, + Start: &plan.FrameBound{ + Type: plan.FrameBound_PRECEDING, + UnBounded: true, + }, + End: &plan.FrameBound{ + Type: plan.FrameBound_FOLLOWING, + UnBounded: true, + }, + }, + }}, + }}, + WindowIdx: 0, + BindingTags: []int32{windowTag}, + }, bindCtx) + + rowNumberCol := &plan.Expr{ + Typ: rowNumberFunc.Typ, + Expr: &plan.Expr_Col{Col: &plan.ColRef{ + RelPos: windowTag, + ColPos: 0, + Name: "__mo_fk_mapping_row_number", + }}, + } + keepFirstRowExpr, err := BindFuncExprImplByPlanExpr( + builder.GetContext(), "=", []*plan.Expr{rowNumberCol, makePlan2Int64ConstExprWithType(1)}) + if err != nil { + return 0, err + } + guardExpr := keepFirstRowExpr + if duplicateErrorType != "" { + guardExpr, err = BindFuncExprImplByPlanExpr( + builder.GetContext(), + "assert", + []*plan.Expr{ + keepFirstRowExpr, + makePlan2StringConstExprWithType(duplicateErrorMessage), + makePlan2StringConstExprWithType(duplicateErrorType), + }, + ) + if err != nil { + return 0, err + } + } + return builder.appendNode(&plan.Node{ + NodeType: plan.Node_FILTER, + Children: []int32{lastNodeID}, + FilterList: []*plan.Expr{guardExpr}, + }, bindCtx), nil +} + func (builder *QueryBuilder) buildUpdateMutationIndexKey( tableDef *plan.TableDef, idxDef *plan.IndexDef, diff --git a/pkg/sql/plan/update_planner_route_test.go b/pkg/sql/plan/update_planner_route_test.go index a8e5c24268b35..fa92dd831d7b1 100644 --- a/pkg/sql/plan/update_planner_route_test.go +++ b/pkg/sql/plan/update_planner_route_test.go @@ -599,26 +599,43 @@ func TestBindUpdateParentForeignKeySafetyGates(t *testing.T) { }) } - t.Run("non unique referenced prefix uses runtime ambiguity guard", func(t *testing.T) { - mock := NewMockOptimizer(true) - prepareEmpDept(mock, planpb.ForeignKeyDef_CASCADE) - dept := mock.ctxt.tables["dept"] - dept.Pkey = &planpb.PrimaryKeyDef{ - Names: []string{"deptno", "dname"}, PkeyColName: "deptno", - } + for _, test := range []struct { + name string + action planpb.ForeignKeyDef_RefAction + windowCount int + ambiguous bool + }{ + {name: "cascade guards distinct child outputs", action: planpb.ForeignKeyDef_CASCADE, windowCount: 2, ambiguous: true}, + {name: "set null only deduplicates child identity", action: planpb.ForeignKeyDef_SET_NULL, windowCount: 1}, + } { + t.Run("non unique referenced prefix "+test.name, func(t *testing.T) { + mock := NewMockOptimizer(true) + prepareEmpDept(mock, test.action) + dept := mock.ctxt.tables["dept"] + dept.Pkey = &planpb.PrimaryKeyDef{ + Names: []string{"deptno", "dname"}, PkeyColName: "deptno", + } - stmt, err := parsers.ParseOne( - mock.CurrentContext().GetContext(), dialect.MYSQL, - "UPDATE dept SET deptno = 2", 1) - require.NoError(t, err) - defer stmt.Free() + stmt, err := parsers.ParseOne( + mock.CurrentContext().GetContext(), dialect.MYSQL, + "UPDATE dept SET deptno = 2", 1) + require.NoError(t, err) + defer stmt.Free() - builder := NewQueryBuilder(planpb.Query_UPDATE, mock.CurrentContext(), false, true) - _, err = builder.bindUpdate(stmt.(*tree.Update), NewBindContext(builder, nil)) - require.NoError(t, err) - require.True(t, updateFkPlanContainsTypedAssert( - builder.qry, foreignKeyAmbiguousMappingAssert)) - }) + builder := NewQueryBuilder(planpb.Query_UPDATE, mock.CurrentContext(), false, true) + _, err = builder.bindUpdate(stmt.(*tree.Update), NewBindContext(builder, nil)) + require.NoError(t, err) + require.Equal(t, test.ambiguous, updateFkPlanContainsTypedAssert( + builder.qry, foreignKeyAmbiguousMappingAssert)) + windowCount := 0 + for _, node := range builder.qry.Nodes { + if node.NodeType == planpb.Node_WINDOW { + windowCount++ + } + } + require.Equal(t, test.windowCount, windowCount) + }) + } } func TestBindUpdateForeignKeySensitivityIncludesImplicitFinalRowChanges(t *testing.T) { diff --git a/pkg/tests/issues/issue_26339_test.go b/pkg/tests/issues/issue_26339_test.go index 441e91e1f8a78..8fedf902ac7d9 100644 --- a/pkg/tests/issues/issue_26339_test.go +++ b/pkg/tests/issues/issue_26339_test.go @@ -127,6 +127,84 @@ func TestIssue26339ForeignKeyExecutionRegressions(t *testing.T) { require.Zero(t, orphanCount) }) + t.Run("non unique referenced prefix validates actual child mappings", func(t *testing.T) { + execSQLRequire(t, ctx, db, "create table "+database+".mapping_parent ("+ + "a int, b int, primary key (a, b))") + execSQLRequire(t, ctx, db, "create table "+database+".mapping_child ("+ + "id int primary key, parent_a int, foreign key (parent_a) "+ + "references "+database+".mapping_parent(a) on update cascade)") + + // No child row means there is no referential-action target to make + // ambiguous, even when changed parents share the same old prefix. + execSQLRequire(t, ctx, db, + "insert into "+database+".mapping_parent values (1, 1), (1, 2)") + execSQLRequire(t, ctx, db, + "update "+database+".mapping_parent set a = b + 1 where a = 1") + var parentCount int + require.NoError(t, db.QueryRowContext(ctx, + "select count(*) from "+database+".mapping_parent where (a, b) in ((2, 1), (3, 2))"). + Scan(&parentCount)) + require.Equal(t, 2, parentCount) + + execSQLRequire(t, ctx, db, "delete from "+database+".mapping_parent") + execSQLRequire(t, ctx, db, + "insert into "+database+".mapping_parent values (1, 1), (1, 2)") + execSQLRequire(t, ctx, db, + "insert into "+database+".mapping_child values (10, 1)") + + // Duplicate parent matches are safe when they produce one identical + // new tuple for the child row. + execSQLRequire(t, ctx, db, + "update "+database+".mapping_parent set a = 2 where a = 1") + var childParent int + require.NoError(t, db.QueryRowContext(ctx, + "select parent_a from "+database+".mapping_child where id = 10").Scan(&childParent)) + require.Equal(t, 2, childParent) + + execSQLRequire(t, ctx, db, "delete from "+database+".mapping_child") + execSQLRequire(t, ctx, db, "delete from "+database+".mapping_parent") + execSQLRequire(t, ctx, db, + "insert into "+database+".mapping_parent values (1, 1), (1, 2)") + execSQLRequire(t, ctx, db, + "insert into "+database+".mapping_child values (10, 1)") + + // Distinct outputs for the same child remain ambiguous and the whole + // statement must roll back. + _, updateErr := db.ExecContext(ctx, + "update "+database+".mapping_parent set a = b + 1 where a = 1") + require.Error(t, updateErr) + var mysqlErr *mysql.MySQLError + require.ErrorAs(t, updateErr, &mysqlErr) + require.Equal(t, uint16(20105), mysqlErr.Number) + require.Contains(t, updateErr.Error(), "ambiguous non-unique referenced-key mapping") + + var originalParentCount, orphanCount int + require.NoError(t, db.QueryRowContext(ctx, + "select count(*) from "+database+".mapping_parent where a = 1").Scan(&originalParentCount)) + require.NoError(t, db.QueryRowContext(ctx, + "select count(*) from "+database+".mapping_child c left join "+ + database+".mapping_parent p on c.parent_a = p.a where p.a is null").Scan(&orphanCount)) + require.Equal(t, 2, originalParentCount) + require.Zero(t, orphanCount) + + execSQLRequire(t, ctx, db, "create table "+database+".set_null_parent ("+ + "a int, b int, primary key (a, b))") + execSQLRequire(t, ctx, db, "create table "+database+".set_null_child ("+ + "id int primary key, parent_a int, foreign key (parent_a) "+ + "references "+database+".set_null_parent(a) on update set null)") + execSQLRequire(t, ctx, db, + "insert into "+database+".set_null_parent values (1, 1), (1, 2)") + execSQLRequire(t, ctx, db, + "insert into "+database+".set_null_child values (10, 1)") + execSQLRequire(t, ctx, db, + "update "+database+".set_null_parent set a = b + 1 where a = 1") + var nullChildCount int + require.NoError(t, db.QueryRowContext(ctx, + "select count(*) from "+database+".set_null_child where id = 10 and parent_a is null"). + Scan(&nullChildCount)) + require.Equal(t, 1, nullChildCount) + }) + t.Run("prepared implicit on update observes enabled foreign key checks", func(t *testing.T) { conn, err := db.Conn(ctx) require.NoError(t, err) From 3199cc00ba2eca427fe9601bf1c22a6428fa0989 Mon Sep 17 00:00:00 2001 From: Cao Kai Date: Mon, 3 Aug 2026 17:09:12 +0800 Subject: [PATCH 22/22] fix(plan): scope parent FK cache sensitivity --- pkg/sql/plan/bind_update.go | 7 ++++- pkg/sql/plan/bind_update_fk.go | 52 ++++++++++++++++++++++++++++++---- pkg/sql/plan/build_test.go | 8 ++++++ 3 files changed, 60 insertions(+), 7 deletions(-) diff --git a/pkg/sql/plan/bind_update.go b/pkg/sql/plan/bind_update.go index d7a81239c8556..80e88d6b63e3e 100644 --- a/pkg/sql/plan/bind_update.go +++ b/pkg/sql/plan/bind_update.go @@ -369,7 +369,12 @@ func (builder *QueryBuilder) bindUpdate(stmt *tree.Update, bindCtx *BindContext) if err != nil { return 0, err } - if updateMayDependOnForeignKeys(dmlCtx, newColName2Idx) { + mayDependOnForeignKeys, err := builder.updateMayDependOnForeignKeys( + bindCtx, dmlCtx, newColName2Idx) + if err != nil { + return 0, err + } + if mayDependOnForeignKeys { // The plan shape and planner route depend on foreign_key_checks. // Preserve that dependency even while checks are disabled so prepared // and generic plan caches rebuild after either session-state transition. diff --git a/pkg/sql/plan/bind_update_fk.go b/pkg/sql/plan/bind_update_fk.go index 7b4f82ef864ba..d955147cc2bc2 100644 --- a/pkg/sql/plan/bind_update_fk.go +++ b/pkg/sql/plan/bind_update_fk.go @@ -193,20 +193,60 @@ func (builder *QueryBuilder) appendUpdateForeignKeyChecks( return lastNodeID, selectNodeTag, selectNode, nil } -func updateMayDependOnForeignKeys( +func (builder *QueryBuilder) updateMayDependOnForeignKeys( + bindCtx *BindContext, dmlCtx *DMLContext, newColName2Idx map[string]int32, -) bool { +) (bool, error) { for i, tableDef := range dmlCtx.tableDefs { if len(dmlCtx.updateCol2Expr[i]) == 0 { continue } - if len(affectedUpdateChildFks(tableDef, dmlCtx.aliases[i], newColName2Idx)) > 0 || - len(tableDef.RefChildTbls) > 0 { - return true + alias := dmlCtx.aliases[i] + if len(affectedUpdateChildFks(tableDef, alias, newColName2Idx)) > 0 { + return true, nil + } + if tableDef == nil || len(tableDef.RefChildTbls) == 0 { + continue + } + + parentColIDToName := make(map[uint64]string, len(tableDef.Cols)) + for _, col := range tableDef.Cols { + parentColIDToName[col.ColId] = col.Name + } + visited := make(map[uint64]struct{}, len(tableDef.RefChildTbls)) + for _, childTableID := range tableDef.RefChildTbls { + if childTableID == 0 { + childTableID = tableDef.TblId + } + if _, ok := visited[childTableID]; ok { + continue + } + visited[childTableID] = struct{}{} + + _, childTableDef, err := builder.compCtx.ResolveById(childTableID, bindCtx.snapshot) + if err != nil { + return false, err + } + if childTableDef == nil { + return false, moerr.NewInternalErrorf( + builder.GetContext(), "foreign-key child table %d not found", childTableID) + } + for _, fk := range childTableDef.Fkeys { + referencesCurrentTable := fk.ForeignTbl == tableDef.TblId || + (fk.ForeignTbl == 0 && childTableDef.TblId == tableDef.TblId) + if !referencesCurrentTable { + continue + } + for _, parentColID := range fk.ForeignCols { + if _, ok := newColName2Idx[alias+"."+parentColIDToName[parentColID]]; ok { + return true, nil + } + } + } } } - return false + return false, nil } func affectedUpdateChildFks( diff --git a/pkg/sql/plan/build_test.go b/pkg/sql/plan/build_test.go index 0328038f14577..83bf57ed64029 100644 --- a/pkg/sql/plan/build_test.go +++ b/pkg/sql/plan/build_test.go @@ -2728,6 +2728,14 @@ func TestPreparedForeignKeyActionsMarkQueryUncacheable(t *testing.T) { require.True(t, query.GetHasForeignKeyAction()) }) + t.Run("unrelated parent update remains cacheable", func(t *testing.T) { + mock := NewMockOptimizer(true) + setMockEmpDeptForeignKeyAction(t, mock, plan.ForeignKeyDef_RESTRICT, plan.ForeignKeyDef_CASCADE) + + query := buildPreparedQuery(t, mock, "prepare stmt1 from update dept set loc = ? where deptno = ?") + require.False(t, query.GetHasForeignKeyAction()) + }) + t.Run("child update remains uncacheable with checks disabled", func(t *testing.T) { mock := NewMockOptimizer(true) setMockEmpDeptForeignKeyAction(t, mock, plan.ForeignKeyDef_SET_NULL, plan.ForeignKeyDef_CASCADE)