Пример #1
0
        void VisitX(SqlInsertClause sc)
        {
            if (sc.Into != null)
            {
                Visit(sc.Into);
            }

            foreach (var c in sc.Items)
            {
                Visit(c);
            }
        }
Пример #2
0
        void Visit1X(SqlInsertClause sc)
        {
            if (sc.Into != null)
            {
                Visit1(sc.Into);
            }

            foreach (var c in sc.Items.ToArray())
            {
                Visit1(c);
            }
        }
Пример #3
0
        SelectQuery(SelectQuery clone, Dictionary <ICloneableElement, ICloneableElement> objectTree, Predicate <ICloneableElement> doClone)
        {
            objectTree.Add(clone, this);
            objectTree.Add(clone.All, All);

            SourceID = Interlocked.Increment(ref SourceIDCounter);

            ICloneableElement parentClone;

            if (clone.ParentSelect != null)
            {
                ParentSelect = objectTree.TryGetValue(clone.ParentSelect, out parentClone) ? (SelectQuery)parentClone : clone.ParentSelect;
            }

            _queryType = clone._queryType;

            if (IsInsert)
            {
                _insert = (SqlInsertClause)clone._insert.Clone(objectTree, doClone);
            }
            if (IsUpdate)
            {
                _update = (SqlUpdateClause)clone._update.Clone(objectTree, doClone);
            }
            if (IsDelete)
            {
                _delete = (SqlDeleteClause)clone._delete.Clone(objectTree, doClone);
            }

            Select   = new SqlSelectClause(this, clone.Select, objectTree, doClone);
            _from    = new SqlFromClause(this, clone._from, objectTree, doClone);
            _where   = new SqlWhereClause(this, clone._where, objectTree, doClone);
            _groupBy = new SqlGroupByClause(this, clone._groupBy, objectTree, doClone);
            _having  = new SqlWhereClause(this, clone._having, objectTree, doClone);
            _orderBy = new SqlOrderByClause(this, clone._orderBy, objectTree, doClone);

            Parameters.AddRange(clone.Parameters.Select(p => (SqlParameter)p.Clone(objectTree, doClone)));
            IsParameterDependent = clone.IsParameterDependent;

            new QueryVisitor().Visit(this, expr =>
            {
                var sb = expr as SelectQuery;

                if (sb != null && sb.ParentSelect == clone)
                {
                    sb.ParentSelect = this;
                }
            });
        }
Пример #4
0
        internal void Init(
            SqlInsertClause insert,
            SqlUpdateClause update,
            SqlDeleteClause delete,
            SqlSelectClause select,
            SqlFromClause from,
            SqlWhereClause where,
            SqlGroupByClause groupBy,
            SqlWhereClause having,
            SqlOrderByClause orderBy,
            List <SqlUnion> unions,
            SelectQuery parentSelect,
            bool parameterDependent,
            List <SqlParameter> parameters)
        {
            _insert              = insert;
            _update              = update;
            _delete              = delete;
            Select               = select;
            _from                = from;
            _where               = where;
            _groupBy             = groupBy;
            _having              = having;
            _orderBy             = orderBy;
            _unions              = unions;
            ParentSelect         = parentSelect;
            IsParameterDependent = parameterDependent;

            Parameters.AddRange(parameters);

            foreach (var col in select.Columns)
            {
                col.Parent = this;
            }

            Select.SetSqlQuery(this);
            _from.SetSqlQuery(this);
            _where.SetSqlQuery(this);
            _groupBy.SetSqlQuery(this);
            _having.SetSqlQuery(this);
            _orderBy.SetSqlQuery(this);
        }
Пример #5
0
 public void Add(SqlSearchCondition?when, SqlInsertClause insert)
 => Inserts.Add(new SqlConditionalInsertClause(insert, when));
Пример #6
0
        IQueryElement?ConvertInternal(IQueryElement?element)
        {
            if (element == null)
            {
                return(null);
            }

            // if element manually added outside to VisistedElements as null, it will be processed continuously.
            // Useful when we have to duplicate such items, especially parameters
            var newElement = GetCurrentReplaced(element);

            if (newElement != null)
            {
                return(newElement);
            }

            Stack.Add(element);
            try
            {
                switch (element.ElementType)
                {
                case QueryElementType.SqlFunction:
                {
                    var func  = (SqlFunction)element;
                    var parms = Convert(func.Parameters);

                    if (parms != null && !ReferenceEquals(parms, func.Parameters))
                    {
                        newElement = new SqlFunction(func.SystemType, func.Name, func.IsAggregate, func.Precedence, parms);
                    }

                    break;
                }

                case QueryElementType.SqlExpression:
                {
                    var expr      = (SqlExpression)element;
                    var parameter = Convert(expr.Parameters);

                    if (parameter != null && !ReferenceEquals(parameter, expr.Parameters))
                    {
                        newElement = new SqlExpression(expr.SystemType, expr.Expr, expr.Precedence, parameter);
                    }

                    break;
                }

                case QueryElementType.SqlBinaryExpression:
                {
                    var bexpr = (SqlBinaryExpression)element;
                    var expr1 = (ISqlExpression?)ConvertInternal(bexpr.Expr1);
                    var expr2 = (ISqlExpression?)ConvertInternal(bexpr.Expr2);

                    if (expr1 != null && !ReferenceEquals(expr1, bexpr.Expr1) ||
                        expr2 != null && !ReferenceEquals(expr2, bexpr.Expr2))
                    {
                        newElement = new SqlBinaryExpression(bexpr.SystemType, expr1 ?? bexpr.Expr1, bexpr.Operation, expr2 ?? bexpr.Expr2, bexpr.Precedence);
                    }

                    break;
                }

                case QueryElementType.SqlTable:
                {
                    var table    = (SqlTable)element;
                    var newTable = (SqlTable)_convert(this, table);

                    if (ReferenceEquals(newTable, table))
                    {
                        var targs = table.TableArguments == null || table.TableArguments.Length == 0 ?
                                    null : Convert(table.TableArguments);

                        if (targs != null && !ReferenceEquals(table.TableArguments, targs))
                        {
                            var newFields = table.Fields.Values.Select(f => new SqlField(f));
                            newTable = new SqlTable(table, newFields, targs);
                        }
                    }

                    if (!ReferenceEquals(table, newTable))
                    {
                        AddVisited(table.All, newTable.All);
                        foreach (var prevField in table.Fields.Values)
                        {
                            if (newTable.Fields.TryGetValue(prevField.Name, out var newField))
                            {
                                AddVisited(prevField, newField);
                            }
                        }
                    }

                    newElement = newTable;

                    break;
                }

                case QueryElementType.SqlCteTable:
                {
                    var table    = (SqlCteTable)element;
                    var newTable = (SqlCteTable)_convert(this, table);

                    if (ReferenceEquals(newTable, table))
                    {
                        var cte = (CteClause?)ConvertInternal(table.Cte);
                        var ce  = cte != null && !ReferenceEquals(table.Cte, cte);

                        if (ce)
                        {
                            var newFields = table.Fields.Values.Select(f => new SqlField(f));
                            newTable = new SqlCteTable(table, newFields, cte !);
                        }
                    }

                    if (!ReferenceEquals(table, newTable))
                    {
                        AddVisited(table.All, newTable.All);
                        foreach (var prevField in table.Fields.Values)
                        {
                            if (newTable.Fields.TryGetValue(prevField.Name, out var newField))
                            {
                                AddVisited(prevField, newField);
                            }
                        }
                    }

                    newElement = newTable;

                    break;
                }

                case QueryElementType.Column:
                {
                    var col  = (SqlColumn)element;
                    var expr = (ISqlExpression?)ConvertInternal(col.Expression);

                    if (expr != null && !ReferenceEquals(expr, col.Expression))
                    {
                        newElement = new SqlColumn(col.Parent, expr, col.RawAlias);
                    }

                    break;
                }

                case QueryElementType.TableSource:
                {
                    var table  = (SqlTableSource)element;
                    var source = (ISqlTableSource?)ConvertInternal(table.Source);
                    var joins  = Convert(table.Joins);

                    List <ISqlExpression[]>?uk = null;
                    if (table.HasUniqueKeys)
                    {
                        uk = ConvertListArray(table.UniqueKeys, null);
                    }

                    if (source != null && !ReferenceEquals(source, table.Source) ||
                        joins != null && !ReferenceEquals(table.Joins, joins))
                    {
                        newElement = new SqlTableSource(
                            source ?? table.Source,
                            table._alias,
                            joins ?? table.Joins,
                            uk ?? (table.HasUniqueKeys ? table.UniqueKeys : null));
                    }

                    break;
                }

                case QueryElementType.JoinedTable:
                {
                    var join  = (SqlJoinedTable)element;
                    var table = (SqlTableSource?)ConvertInternal(join.Table);
                    var cond  = (SqlSearchCondition?)ConvertInternal(join.Condition);

                    if (table != null && !ReferenceEquals(table, join.Table) ||
                        cond != null && !ReferenceEquals(cond, join.Condition))
                    {
                        newElement = new SqlJoinedTable(join.JoinType, table ?? join.Table, join.IsWeak, cond ?? join.Condition);
                    }

                    break;
                }

                case QueryElementType.SearchCondition:
                {
                    var sc    = (SqlSearchCondition)element;
                    var conds = Convert(sc.Conditions);

                    if (conds != null && !ReferenceEquals(sc.Conditions, conds))
                    {
                        newElement = new SqlSearchCondition(conds);
                    }

                    break;
                }

                case QueryElementType.Condition:
                {
                    var c = (SqlCondition)element;
                    var p = (ISqlPredicate?)ConvertInternal(c.Predicate);

                    if (p != null && !ReferenceEquals(c.Predicate, p))
                    {
                        newElement = new SqlCondition(c.IsNot, p, c.IsOr);
                    }

                    break;
                }

                case QueryElementType.ExprPredicate:
                {
                    var p = (SqlPredicate.Expr)element;
                    var e = (ISqlExpression?)ConvertInternal(p.Expr1);

                    if (e != null && !ReferenceEquals(p.Expr1, e))
                    {
                        newElement = new SqlPredicate.Expr(e, p.Precedence);
                    }

                    break;
                }

                case QueryElementType.NotExprPredicate:
                {
                    var p = (SqlPredicate.NotExpr)element;
                    var e = (ISqlExpression?)ConvertInternal(p.Expr1);

                    if (e != null && !ReferenceEquals(p.Expr1, e))
                    {
                        newElement = new SqlPredicate.NotExpr(e, p.IsNot, p.Precedence);
                    }

                    break;
                }

                case QueryElementType.ExprExprPredicate:
                {
                    var p  = (SqlPredicate.ExprExpr)element;
                    var e1 = (ISqlExpression?)ConvertInternal(p.Expr1);
                    var e2 = (ISqlExpression?)ConvertInternal(p.Expr2);

                    if (e1 != null && !ReferenceEquals(p.Expr1, e1) || e2 != null && !ReferenceEquals(p.Expr2, e2))
                    {
                        newElement = new SqlPredicate.ExprExpr(e1 ?? p.Expr1, p.Operator, e2 ?? p.Expr2);
                    }

                    break;
                }

                case QueryElementType.LikePredicate:
                {
                    var p  = (SqlPredicate.Like)element;
                    var e1 = (ISqlExpression?)ConvertInternal(p.Expr1);
                    var e2 = (ISqlExpression?)ConvertInternal(p.Expr2);
                    var es = (ISqlExpression?)ConvertInternal(p.Escape);

                    if (e1 != null && !ReferenceEquals(p.Expr1, e1) ||
                        e2 != null && !ReferenceEquals(p.Expr2, e2) ||
                        es != null && !ReferenceEquals(p.Escape, es))
                    {
                        newElement = new SqlPredicate.Like(e1 ?? p.Expr1, p.IsNot, e2 ?? p.Expr2, es ?? p.Escape, p.IsSqlLike);
                    }

                    break;
                }

                case QueryElementType.BetweenPredicate:
                {
                    var p  = (SqlPredicate.Between)element;
                    var e1 = (ISqlExpression?)ConvertInternal(p.Expr1);
                    var e2 = (ISqlExpression?)ConvertInternal(p.Expr2);
                    var e3 = (ISqlExpression?)ConvertInternal(p.Expr3);

                    if (e1 != null && !ReferenceEquals(p.Expr1, e1) ||
                        e2 != null && !ReferenceEquals(p.Expr2, e2) ||
                        e3 != null && !ReferenceEquals(p.Expr3, e3))
                    {
                        newElement = new SqlPredicate.Between(e1 ?? p.Expr1, p.IsNot, e2 ?? p.Expr2, e3 ?? p.Expr3);
                    }

                    break;
                }

                case QueryElementType.IsNullPredicate:
                {
                    var p = (SqlPredicate.IsNull)element;
                    var e = (ISqlExpression?)ConvertInternal(p.Expr1);

                    if (e != null && !ReferenceEquals(p.Expr1, e))
                    {
                        newElement = new SqlPredicate.IsNull(e, p.IsNot);
                    }

                    break;
                }

                case QueryElementType.InSubQueryPredicate:
                {
                    var p = (SqlPredicate.InSubQuery)element;
                    var e = (ISqlExpression?)ConvertInternal(p.Expr1);
                    var q = (SelectQuery?)ConvertInternal(p.SubQuery);

                    if (e != null && !ReferenceEquals(p.Expr1, e) || q != null && !ReferenceEquals(p.SubQuery, q))
                    {
                        newElement = new SqlPredicate.InSubQuery(e ?? p.Expr1, p.IsNot, q ?? p.SubQuery);
                    }

                    break;
                }

                case QueryElementType.InListPredicate:
                {
                    var p = (SqlPredicate.InList)element;
                    var e = (ISqlExpression?)ConvertInternal(p.Expr1);
                    var v = Convert(p.Values);

                    if (e != null && !ReferenceEquals(p.Expr1, e) || v != null && !ReferenceEquals(p.Values, v))
                    {
                        newElement = new SqlPredicate.InList(e ?? p.Expr1, p.IsNot, v ?? p.Values);
                    }

                    break;
                }

                case QueryElementType.FuncLikePredicate:
                {
                    var p = (SqlPredicate.FuncLike)element;
                    var f = (SqlFunction?)ConvertInternal(p.Function);

                    if (f != null && !ReferenceEquals(p.Function, f))
                    {
                        newElement = new SqlPredicate.FuncLike(f);
                    }

                    break;
                }

                case QueryElementType.SetExpression:
                {
                    var s = (SqlSetExpression)element;
                    var c = (ISqlExpression?)ConvertInternal(s.Column);
                    var e = (ISqlExpression?)ConvertInternal(s.Expression);

                    if (c != null && !ReferenceEquals(s.Column, c) || e != null && !ReferenceEquals(s.Expression, e))
                    {
                        newElement = new SqlSetExpression(c ?? s.Column, e ?? s.Expression !);
                    }

                    break;
                }

                case QueryElementType.InsertClause:
                {
                    var s = (SqlInsertClause)element;
                    var t = s.Into != null ? (SqlTable?)ConvertInternal(s.Into) : null;
                    var i = Convert(s.Items);

                    if (t != null && !ReferenceEquals(s.Into, t) || i != null && !ReferenceEquals(s.Items, i))
                    {
                        var sc = new SqlInsertClause {
                            Into = t ?? s.Into
                        };

                        sc.Items.AddRange(i ?? s.Items);
                        sc.WithIdentity = s.WithIdentity;

                        newElement = sc;
                    }

                    break;
                }

                case QueryElementType.UpdateClause:
                {
                    var s = (SqlUpdateClause)element;
                    var t = s.Table != null ? (SqlTable?)ConvertInternal(s.Table) : null;
                    var i = Convert(s.Items);
                    var k = Convert(s.Keys);

                    if (t != null && !ReferenceEquals(s.Table, t) ||
                        i != null && !ReferenceEquals(s.Items, i) ||
                        k != null && !ReferenceEquals(s.Keys, k))
                    {
                        var sc = new SqlUpdateClause {
                            Table = t ?? s.Table
                        };

                        sc.Items.AddRange(i ?? s.Items);
                        sc.Keys.AddRange(k ?? s.Keys);

                        newElement = sc;
                    }

                    break;
                }

                case QueryElementType.SelectStatement:
                {
                    var s           = (SqlSelectStatement)element;
                    var selectQuery = (SelectQuery?)ConvertInternal(s.SelectQuery);
                    var with        = s.With != null ? (SqlWithClause?)ConvertInternal(s.With) : null;
                    var ps          = ConvertSafe(s.Parameters);

                    if (ps != null && !ReferenceEquals(s.Parameters, ps) ||
                        selectQuery != null && !ReferenceEquals(s.SelectQuery, selectQuery) ||
                        with != null && !ReferenceEquals(s.With, with))
                    {
                        newElement = new SqlSelectStatement(selectQuery ?? s.SelectQuery);
                        ((SqlSelectStatement)newElement).Parameters.AddRange(ps ?? s.Parameters);
                        ((SqlSelectStatement)newElement).With = with ?? s.With;
                        CorrectQueryHierarchy(((SqlSelectStatement)newElement).SelectQuery);
                    }

                    break;
                }

                case QueryElementType.InsertStatement:
                {
                    var s           = (SqlInsertStatement)element;
                    var selectQuery = (SelectQuery?    )ConvertInternal(s.SelectQuery);
                    var insert      = (SqlInsertClause?)ConvertInternal(s.Insert);
                    var with        = s.With != null ? (SqlWithClause?)ConvertInternal(s.With) : null;
                    var ps          = ConvertSafe(s.Parameters);

                    if (insert != null && !ReferenceEquals(s.Insert, insert) ||
                        ps != null && !ReferenceEquals(s.Parameters, ps) ||
                        selectQuery != null && !ReferenceEquals(s.SelectQuery, selectQuery) ||
                        with != null && !ReferenceEquals(s.With, with))
                    {
                        newElement = new SqlInsertStatement(selectQuery ?? s.SelectQuery)
                        {
                            Insert = insert ?? s.Insert
                        };
                        ((SqlInsertStatement)newElement).Parameters.AddRange(ps ?? s.Parameters);
                        ((SqlInsertStatement)newElement).With = with ?? s.With;
                        CorrectQueryHierarchy(((SqlInsertStatement)newElement).SelectQuery);
                    }

                    break;
                }

                case QueryElementType.UpdateStatement:
                {
                    var s           = (SqlUpdateStatement)element;
                    var selectQuery = (SelectQuery?    )ConvertInternal(s.SelectQuery);
                    var update      = (SqlUpdateClause?)ConvertInternal(s.Update);
                    var with        = s.With != null ? (SqlWithClause?)ConvertInternal(s.With) : null;
                    var ps          = ConvertSafe(s.Parameters);

                    if (update != null && !ReferenceEquals(s.Update, update) ||
                        ps != null && !ReferenceEquals(s.Parameters, ps) ||
                        selectQuery != null && !ReferenceEquals(s.SelectQuery, selectQuery) ||
                        with != null && !ReferenceEquals(s.With, with))
                    {
                        newElement = new SqlUpdateStatement(selectQuery ?? s.SelectQuery)
                        {
                            Update = update ?? s.Update
                        };
                        ((SqlUpdateStatement)newElement).Parameters.AddRange(ps ?? s.Parameters);
                        ((SqlUpdateStatement)newElement).With = with ?? s.With;
                        CorrectQueryHierarchy(((SqlUpdateStatement)newElement).SelectQuery);
                    }

                    break;
                }

                case QueryElementType.InsertOrUpdateStatement:
                {
                    var s = (SqlInsertOrUpdateStatement)element;

                    var selectQuery = (SelectQuery?    )ConvertInternal(s.SelectQuery);
                    var insert      = (SqlInsertClause?)ConvertInternal(s.Insert);
                    var update      = (SqlUpdateClause?)ConvertInternal(s.Update);
                    var with        = s.With != null ? (SqlWithClause?)ConvertInternal(s.With) : null;
                    var ps          = ConvertSafe(s.Parameters);

                    if (insert != null && !ReferenceEquals(s.Insert, insert) ||
                        update != null && !ReferenceEquals(s.Update, update) ||
                        ps != null && !ReferenceEquals(s.Parameters, ps) ||
                        selectQuery != null && !ReferenceEquals(s.SelectQuery, selectQuery) ||
                        with != null && !ReferenceEquals(s.With, with))
                    {
                        newElement = new SqlInsertOrUpdateStatement(selectQuery ?? s.SelectQuery)
                        {
                            Insert = insert ?? s.Insert, Update = update ?? s.Update
                        };
                        ((SqlInsertOrUpdateStatement)newElement).Parameters.AddRange(ps ?? s.Parameters);
                        ((SqlInsertOrUpdateStatement)newElement).With = with ?? s.With;
                        CorrectQueryHierarchy(((SqlInsertOrUpdateStatement)newElement).SelectQuery);
                    }

                    break;
                }

                case QueryElementType.DeleteStatement:
                {
                    var s           = (SqlDeleteStatement)element;
                    var selectQuery = s.SelectQuery != null ? (SelectQuery?)ConvertInternal(s.SelectQuery) : null;
                    var table       = s.Table != null ? (SqlTable?)ConvertInternal(s.Table) : null;
                    var top         = s.Top != null ? (ISqlExpression?)ConvertInternal(s.Top) : null;
                    var with        = s.With != null ? (SqlWithClause?)ConvertInternal(s.With) : null;
                    var ps          = ConvertSafe(s.Parameters);

                    if (table != null && !ReferenceEquals(s.Table, table) ||
                        top != null && !ReferenceEquals(s.Top, top) ||
                        ps != null && !ReferenceEquals(s.Parameters, ps) ||
                        selectQuery != null && !ReferenceEquals(s.SelectQuery, selectQuery) ||
                        with != null && !ReferenceEquals(s.With, with))
                    {
                        newElement = new SqlDeleteStatement
                        {
                            Table                = table ?? s.Table,
                            SelectQuery          = selectQuery ?? s.SelectQuery,
                            Top                  = top ?? s.Top !,
                            IsParameterDependent = s.IsParameterDependent
                        };
                        ((SqlDeleteStatement)newElement).Parameters.AddRange(ps ?? s.Parameters);
                        ((SqlDeleteStatement)newElement).With = with ?? s.With;
                        CorrectQueryHierarchy(((SqlDeleteStatement)newElement).SelectQuery);
                    }

                    break;
                }

                case QueryElementType.CreateTableStatement:
                {
                    var s  = (SqlCreateTableStatement)element;
                    var t  = s.Table != null ? (SqlTable?)ConvertInternal(s.Table) : null;
                    var ps = ConvertSafe(s.Parameters);

                    if (t != null && !ReferenceEquals(s.Table, t) ||
                        ps != null && !ReferenceEquals(s.Parameters, ps))
                    {
                        newElement = new SqlCreateTableStatement {
                            Table = t ?? s.Table
                        };
                        if (ps != null)
                        {
                            ((SqlCreateTableStatement)newElement).Parameters.AddRange(ps);
                        }
                        else
                        {
                            ((SqlCreateTableStatement)newElement).Parameters.AddRange(s.Parameters);
                        }
                    }

                    break;
                }

                case QueryElementType.DropTableStatement:
                {
                    var s  = (SqlDropTableStatement)element;
                    var t  = s.Table != null ? (SqlTable?)ConvertInternal(s.Table) : null;
                    var ps = ConvertSafe(s.Parameters);

                    if (t != null && !ReferenceEquals(s.Table, t) ||
                        ps != null && !ReferenceEquals(s.Parameters, ps))
                    {
                        newElement = new SqlDropTableStatement(s.IfExists)
                        {
                            Table = t ?? s.Table
                        };
                        if (ps != null)
                        {
                            ((SqlDropTableStatement)newElement).Parameters.AddRange(ps);
                        }
                        else
                        {
                            ((SqlDropTableStatement)newElement).Parameters.AddRange(s.Parameters);
                        }
                    }

                    break;
                }

                case QueryElementType.SelectClause:
                {
                    var sc   = (SqlSelectClause)element;
                    var cols = Convert(sc.Columns, CloneColumn);
                    var take = (ISqlExpression?)ConvertInternal(sc.TakeValue);
                    var skip = (ISqlExpression?)ConvertInternal(sc.SkipValue);

                    if (
                        cols != null && !ReferenceEquals(sc.Columns, cols) ||
                        take != null && !ReferenceEquals(sc.TakeValue, take) ||
                        skip != null && !ReferenceEquals(sc.SkipValue, skip))
                    {
                        newElement = new SqlSelectClause(sc.IsDistinct, take ?? sc.TakeValue, sc.TakeHints, skip ?? sc.SkipValue, cols ?? sc.Columns);
                        ((SqlSelectClause)newElement).SetSqlQuery(sc.SelectQuery);
                    }
Пример #7
0
 public SqlConditionalInsertClause(SqlInsertClause insert, SqlSearchCondition?when)
 {
     Insert = insert;
     When   = when;
 }
Пример #8
0
 public void ClearInsert()
 {
     _insert = null;
 }