Beispiel #1
0
 public virtual void WriteGroupByExpressionQueryParameter(
     GroupByQueryExpression groupByQueryExpression,
     QueryExpressionVisitor queryExpressionVisitor
     )
 {
     WriteIfNotNull(groupByQueryExpression.Expression, queryExpressionVisitor);
 }
Beispiel #2
0
 public virtual void WriteMultipleStatements(
     MultipleStatementQueryExpression multipleStatementQueryExpression,
     QueryExpressionVisitor queryExpressionVisitor
     )
 {
     WriteExpressionCollection(multipleStatementQueryExpression.Queries, queryExpressionVisitor, seperator: " ");
 }
 public void Visit(QueryExpressionVisitor visitor)
 {
     foreach (var query in Queries)
     {
         visitor.Visit(query);
     }
 }
Beispiel #4
0
 public virtual void WriteHavingQueryParameters(
     HavingQueryExpression whereQueryExpression,
     QueryExpressionVisitor queryExpressionVisitor)
 {
     queryText.Append(" HAVING ");
     queryExpressionVisitor.Visit(whereQueryExpression.Expression);
 }
Beispiel #5
0
 public virtual void WriteFromQueryParameter(
     FromQueryExpression fromQueryExpression,
     QueryExpressionVisitor queryExpressionVisitor)
 {
     queryText.Append(" FROM ");
     queryExpressionVisitor.Visit(fromQueryExpression.Expression);
 }
Beispiel #6
0
 public virtual void WriteWhereQueryParameters(
     WhereQueryExpression whereQueryExpression,
     QueryExpressionVisitor queryExpressionVisitor)
 {
     queryText.Append(" WHERE ");
     queryExpressionVisitor.Visit(whereQueryExpression.Expression);
 }
Beispiel #7
0
        public virtual void WriteDeleteStatement(
            DeleteStatementQueryExpression deleteStatementQueryExpression,
            QueryExpressionVisitor queryExpressionVisitor
            )
        {
            statementStack.Push(deleteStatementQueryExpression);

            if (IsSubQuery())
            {
                queryText.Append(" ( ");
            }

            queryText.Append("DELETE ");
            WriteIfNotNull(deleteStatementQueryExpression.From, queryExpressionVisitor);
            WriteIfNotNull(deleteStatementQueryExpression.Where, queryExpressionVisitor);

            if (IsSubQuery())
            {
                queryText.Append(" ) ");
            }
            else
            {
                queryText.Append("; ");
            }

            statementStack.Pop();
        }
Beispiel #8
0
        public virtual void WriteIsInOperator(
            IsInOperatorQueryExpression isInFunctionCallQueryExpression,
            QueryExpressionVisitor queryExpressionVisitor
            )
        {
            queryExpressionVisitor.Visit(isInFunctionCallQueryExpression.Expression);
            if (isInFunctionCallQueryExpression.IsNotIn)
            {
                queryText.Append(" NOT ");
            }
            queryText.Append(" IN ");
            var expressionsAreQueries = isInFunctionCallQueryExpression.InExpressions[0] is ExecutableQueryExpression;

            if (!expressionsAreQueries)
            {
                queryText.Append("(");
            }

            WriteExpressionCollection(isInFunctionCallQueryExpression.InExpressions, queryExpressionVisitor);

            if (!expressionsAreQueries)
            {
                queryText.Append(")");
            }
            queryText.Append(" ");
        }
Beispiel #9
0
        private static async Task ExecuteSubExpression(
            QueryContext context,
            QueryExpressionVisitor visitor,
            IQueryExecutor executor,
            Expression expression,
            CancellationToken cancellationToken)
        {
            // get element type
            Type elementType = null;
            var  queryType   = expression.Type.FindGenericType(typeof(IQueryable <>));

            if (queryType != null)
            {
                elementType = queryType.GetGenericArguments()[0];
            }

            var query  = visitor.BaseQuery.Provider.CreateQuery(expression);
            var method = typeof(IQueryExecutor)
                         .GetMethod("ExecuteQueryAsync")
                         .MakeGenericMethod(elementType);
            var parameters = new object[]
            {
                context, query, cancellationToken
            };
            var task   = method.Invoke(executor, parameters) as Task <QueryResult>;
            var result = await task.ConfigureAwait(false);

            var any = result.Results.Cast <object>().Any();

            if (!any)
            {
                // Which means previous expression does not have result, and should throw ResourceNotFoundException.
                throw new ResourceNotFoundException(Resources.ResourceNotFound);
            }
        }
Beispiel #10
0
 public virtual void WriteGroupByQueryParameter(
     GroupByCollectionQueryExpression groupByCollectionQueryExpression,
     QueryExpressionVisitor queryExpressionVisitor
     )
 {
     queryText.Append(" GROUP BY ");
     WriteExpressionCollection(groupByCollectionQueryExpression.Expressions, queryExpressionVisitor);
 }
Beispiel #11
0
 public virtual void WriteOrderByQueryParameter(
     OrderByCollectionQueryExpression orderByCollectionQuery,
     QueryExpressionVisitor queryExpressionVisitor
     )
 {
     queryText.Append(" ORDER BY ");
     WriteExpressionCollection(orderByCollectionQuery.Expressions, queryExpressionVisitor);
 }
Beispiel #12
0
 public virtual void WriteIntoQueryParameter(
     IntoQueryExpression intoQueryExpression,
     QueryExpressionVisitor queryExpressionVisitor
     )
 {
     queryText.Append(" INTO ");
     queryExpressionVisitor.Visit(intoQueryExpression.Expression);
 }
Beispiel #13
0
 protected void WriteIfNotNull(QueryExpression queryExpression, QueryExpressionVisitor queryExpressionVisitor)
 {
     if (queryExpression == null)
     {
         return;
     }
     queryExpressionVisitor.Visit(queryExpression);
 }
 public override void WriteExtension(QueryExpression queryExpression, QueryExpressionVisitor queryExpressionVisitor)
 {
     if (queryExpression is ISqlServerExtension sqlServerExtension)
     {
         sqlServerExtension.Write(this, queryExpressionVisitor);
     }
     base.WriteExtension(queryExpression, queryExpressionVisitor);
 }
Beispiel #15
0
 public virtual void WriteCountFunction(
     CountFunctionCallQueryExpression countFunctionCallQueryExpression,
     QueryExpressionVisitor queryExpressionVisitor)
 {
     queryText.Append(" COUNT(");
     WriteIfNotNull(countFunctionCallQueryExpression.Expression, queryExpressionVisitor);
     queryText.Append(") ");
 }
Beispiel #16
0
 public override void WriteExtension(QueryExpression queryExpression, QueryExpressionVisitor queryExpressionVisitor)
 {
     if (queryExpression is IPostgresqlExtension postgresqlExtension)
     {
         postgresqlExtension.Write(this, queryExpressionVisitor);
     }
     base.WriteExtension(queryExpression, queryExpressionVisitor);
 }
Beispiel #17
0
 public override void WriteExtension(QueryExpression queryExpression, QueryExpressionVisitor queryExpressionVisitor)
 {
     if (queryExpression is IMySQLExtension mysqlExtension)
     {
         mysqlExtension.Write(this, queryExpressionVisitor);
     }
     base.WriteExtension(queryExpression, queryExpressionVisitor);
 }
Beispiel #18
0
 public virtual void WriteMaxFunction(
     MaxFunctionCallQueryExpression maxFunctionCallQueryExpression,
     QueryExpressionVisitor queryExpressionVisitor)
 {
     queryText.Append(" MAX(");
     WriteIfNotNull(maxFunctionCallQueryExpression.Expression, queryExpressionVisitor);
     queryText.Append(") ");
 }
        public static IEnumerable <Expression> Query(this Expression self, Func <Expression, bool> predicate)
        {
            var queryVisitor = new QueryExpressionVisitor(predicate);

            queryVisitor.Visit(self);

            return(queryVisitor.Expressions);
        }
Beispiel #20
0
 public virtual void WriteConcatFunction(
     ConcatFunctionCallQueryExpression concatFunctionCallQueryExpression,
     QueryExpressionVisitor queryExpressionVisitor
     )
 {
     queryText.Append(" CONCAT(");
     WriteExpressionCollection(concatFunctionCallQueryExpression.Expressions, queryExpressionVisitor);
     queryText.Append(") ");
 }
Beispiel #21
0
 public virtual void WriteAsOperator(
     AsOperatorQueryExpression asFunctionCallQueryExpression,
     QueryExpressionVisitor queryExpressionVisitor
     )
 {
     queryExpressionVisitor.Visit(asFunctionCallQueryExpression.Expression);
     queryText.Append(" AS ");
     WriteIdentifier(asFunctionCallQueryExpression.Alias, queryExpressionVisitor);
 }
Beispiel #22
0
        private static async Task CheckSubExpressionResult(
            QueryContext context,
            CancellationToken cancellationToken,
            IEnumerable enumerableResult,
            QueryExpressionVisitor visitor,
            IQueryExecutor executor,
            Expression expression)
        {
            if (enumerableResult.GetEnumerator().MoveNext())
            {
                // If there is some result, will not have additional processing
                return;
            }

            var methodCallExpression = expression as MethodCallExpression;

            // This will remove unneeded statement which includes $expand, $select,$top,$skip,$orderby
            methodCallExpression = methodCallExpression.RemoveUnneededStatement();
            if (methodCallExpression == null || methodCallExpression.Arguments.Count != 2)
            {
                return;
            }

            if (methodCallExpression.Method.Name == ExpressionMethodNameOfWhere)
            {
                // Throw exception if key as last where statement, or remove $filter where statement
                methodCallExpression = CheckWhereCondition(methodCallExpression);
                if (methodCallExpression == null || methodCallExpression.Arguments.Count != 2)
                {
                    return;
                }

                // Call without $filter where statement and with Key where statement
                if (methodCallExpression.Method.Name == ExpressionMethodNameOfWhere)
                {
                    // The last where from $filter is removed and run with key where statement
                    await ExecuteSubExpression(context, cancellationToken, visitor, executor, methodCallExpression);

                    return;
                }
            }

            if (methodCallExpression.Method.Name != ExpressionMethodNameOfSelect &&
                methodCallExpression.Method.Name != ExpressionMethodNameOfSelectMany)
            {
                // If last statement is not select property, will no further checking
                return;
            }

            var subExpression = methodCallExpression.Arguments[0];

            // Remove appended statement like Where(Param_0 => (Param_0.Prop != null)) if there is one
            subExpression = subExpression.RemoveAppendWhereStatement();

            await ExecuteSubExpression(context, cancellationToken, visitor, executor, subExpression);
        }
Beispiel #23
0
 public virtual void WriteOrderByExpressionQueryParameter(
     OrderByQueryExpression orderByQueryExpression,
     QueryExpressionVisitor queryExpressionVisitor
     )
 {
     WriteIfNotNull(orderByQueryExpression.Expression, queryExpressionVisitor);
     if (orderByQueryExpression.Direction == OrderByDirection.Descending)
     {
         queryText.Append(" DESC ");
     }
 }
Beispiel #24
0
 public virtual void WriteLastInsertedIdFunction(
     LastInsertedIdFunctionCallExpression lastInsertedIdFunctionCallExpression,
     QueryExpressionVisitor queryExpressionVisitor
     )
 {
     //  todo: decide what to do about this
     //  last insert id is literally different on every single platform
     //  the obvious thing to do would be to make QueryWriter abstract
     //  but that means writing an implementation of a writer for testing
     //  purposes and use in other contexts I can't think of now
     throw new NotImplementedException();
 }
Beispiel #25
0
        public virtual void WriteLimitExpressionParameter(
            LimitQueryExpression limitQueryExpression,
            QueryExpressionVisitor queryExpressionVisitor
            )
        {
            queryText.Append(" LIMIT ");
            WriteIfNotNull(limitQueryExpression.Limit, queryExpressionVisitor);

            if (limitQueryExpression.Offset != null)
            {
                queryText.Append(" OFFSET ");
                WriteIfNotNull(limitQueryExpression.Offset, queryExpressionVisitor);
            }
        }
Beispiel #26
0
        public virtual void WriteSelectStatement(
            SelectStatementQueryExpression selectStatementQueryExpression,
            QueryExpressionVisitor queryExpressionVisitor)
        {
            statementStack.Push(selectStatementQueryExpression);

            if (IsSubQuery())
            {
                queryText.Append(" ( ");
            }

            queryText.Append("SELECT ");
            if (selectStatementQueryExpression.Projection == null || selectStatementQueryExpression.Projection.Expressions == null ||
                selectStatementQueryExpression.Projection.Expressions.Length < 1)
            {
                throw new ProjectionMissingException("SELECT query must include a projection.");
            }
            queryExpressionVisitor.Visit(selectStatementQueryExpression.Projection);

            WriteIfNotNull(selectStatementQueryExpression.From, queryExpressionVisitor);

            if (selectStatementQueryExpression.Joins != null && selectStatementQueryExpression.Joins.Length > 0)
            {
                WriteExpressionCollection(selectStatementQueryExpression.Joins, queryExpressionVisitor, seperator: " ");
            }

            WriteIfNotNull(selectStatementQueryExpression.Where, queryExpressionVisitor);

            WriteIfNotNull(selectStatementQueryExpression.GroupBy, queryExpressionVisitor);

            WriteIfNotNull(selectStatementQueryExpression.Having, queryExpressionVisitor);

            WriteIfNotNull(selectStatementQueryExpression.OrderBy, queryExpressionVisitor);

            WriteIfNotNull(selectStatementQueryExpression.Limit, queryExpressionVisitor);

            if (IsSubQuery())
            {
                queryText.Append(" ) ");
            }
            else
            {
                queryText.Append("; ");
            }

            statementStack.Pop();
        }
Beispiel #27
0
        DbCommandFactor GenerateCommandFactor()
        {
            IQueryState qs   = QueryExpressionVisitor.VisitQueryExpression(this._query.QueryExpression);
            MappingData data = qs.GenerateMappingData();

            IObjectActivator objectActivator;

            objectActivator = data.ObjectActivatorCreator.CreateObjectActivator();

            IDbExpressionTranslator translator = this._query.DbContext._dbContextServiceProvider.CreateDbExpressionTranslator();
            List <DbParam>          parameters;
            string cmdText = translator.Translate(data.SqlQuery, out parameters);

            DbCommandFactor commandFactor = new DbCommandFactor(objectActivator, cmdText, parameters.ToArray());

            return(commandFactor);
        }
Beispiel #28
0
 public virtual void WriteIdentifier(IdentifierQueryExpression identifierQueryExpression,
                                     QueryExpressionVisitor queryExpressionVisitor)
 {
     if (identifierQueryExpression is NestedIdentifierQueryExpression nestedIdentifierQueryExpression &&
         nestedIdentifierQueryExpression.ParentIdentifier != null)
     {
         WriteIdentifier(nestedIdentifierQueryExpression.ParentIdentifier, queryExpressionVisitor);
         queryText.Append(".");
     }
     if (identifierQueryExpression.IdentifierName == "*")
     {
         queryText.Append(" * ");
     }
     else
     {
         queryText.Append($" [{identifierQueryExpression.IdentifierName}] ");
     }
 }
Beispiel #29
0
        public virtual void WriteInsertStatement(
            InsertStatementQueryExpression insertStatementQueryExpression,
            QueryExpressionVisitor queryExpressionVisitor
            )
        {
            statementStack.Push(insertStatementQueryExpression);

            if (IsSubQuery())
            {
                queryText.Append(" ( ");
            }

            queryText.Append("INSERT ");
            WriteIfNotNull(insertStatementQueryExpression.Into, queryExpressionVisitor);

            queryText.Append(" ( ");
            WriteExpressionCollection(insertStatementQueryExpression.Columns, queryExpressionVisitor);
            queryText.Append(" ) ");
            queryText.Append(" VALUES ");

            for (var i = 0; i < insertStatementQueryExpression.RowsExpressions.Length; i++)
            {
                queryText.Append(" ( ");
                WriteExpressionCollection(insertStatementQueryExpression.RowsExpressions[i], queryExpressionVisitor);
                queryText.Append(" ) ");
                if (i < insertStatementQueryExpression.RowsExpressions.Length - 1)
                {
                    queryText.Append(", ");
                }
            }

            if (IsSubQuery())
            {
                queryText.Append(" ) ");
            }
            else
            {
                queryText.Append("; ");
            }

            statementStack.Pop();
        }
Beispiel #30
0
        private static async Task ExecuteSubExpression(
            QueryContext context,
            QueryExpressionVisitor visitor,
            IQueryExecutor executor,
            Expression expression,
            CancellationToken cancellationToken)
        {
            // get element type
            Type elementType = null;
            var  queryType   = expression.Type.FindGenericType(typeof(IQueryable <>));

            if (queryType != null)
            {
                elementType = queryType.GetGenericArguments()[0];
            }

            var query  = visitor.BaseQuery.Provider.CreateQuery(expression);
            var method = typeof(IQueryExecutor)
                         .GetMethod("ExecuteQueryAsync")
                         .MakeGenericMethod(elementType);
            var parameters = new object[]
            {
                context, query, cancellationToken
            };

            var task = method.Invoke(executor, parameters) as Task <QueryResult>;
            await task.ConfigureAwait(false);

            // RWM: This code currently returns 404s if there are no results, instead of returning empty queries.
            //      This means that legit EntitySets that just have no data in the table also return 404. No bueno.

            //var task = method.Invoke(executor, parameters) as Task<QueryResult>;
            //var result = await task.ConfigureAwait(false);

            //var any = result.Results.Cast<object>().Any();
            //if (!any)
            //{
            //    // Which means previous expression does not have result, and should throw ResourceNotFoundException.
            //    throw new ResourceNotFoundException(Resources.ResourceNotFound);
            //}
        }
        /// <summary>
        /// Asynchronously executes the query flow.
        /// </summary>
        /// <param name="context">
        /// The query context.
        /// </param>
        /// <param name="cancellationToken">
        /// A cancellation token.
        /// </param>
        /// <returns>
        /// A task that represents the asynchronous
        /// operation whose result is a query result.
        /// </returns>
        public static async Task<QueryResult> QueryAsync(
            QueryContext context,
            CancellationToken cancellationToken)
        {
            Ensure.NotNull(context, "context");

            // process query expression
            var expression = context.Request.Expression;
            var visitor = new QueryExpressionVisitor(context);
            expression = visitor.Visit(expression);

            // get element type
            Type elementType = null;
            var queryType = expression.Type.FindGenericType(typeof(IQueryable<>));
            if (queryType != null)
            {
                elementType = queryType.GetGenericArguments()[0];
            }

            // append count expression if requested
            if (elementType != null && context.Request.ShouldReturnCount)
            {
                expression = ExpressionHelpers.Count(expression, elementType);
                elementType = null; // now return type is single int
            }

            // execute query
            QueryResult result;
            var executor = context.GetHookHandler<IQueryExecutor>();
            if (executor == null)
            {
                throw new NotSupportedException(Resources.QueryExecutorMissing);
            }

            if (elementType != null)
            {
                var query = visitor.BaseQuery.Provider.CreateQuery(expression);
                var method = typeof(IQueryExecutor)
                    .GetMethod("ExecuteQueryAsync")
                    .MakeGenericMethod(elementType);
                var parameters = new object[]
                {
                    context, query, cancellationToken
                };
                var task = method.Invoke(executor, parameters) as Task<QueryResult>;
                result = await task;
            }
            else
            {
                var method = typeof(IQueryExecutor)
                    .GetMethod("ExecuteSingleAsync")
                    .MakeGenericMethod(expression.Type);
                var parameters = new object[]
                {
                    context, visitor.BaseQuery, expression, cancellationToken
                };
                var task = method.Invoke(executor, parameters) as Task<QueryResult>;
                result = await task;
            }

            if (result != null)
            {
                result.ResultsSource = visitor.EntitySet;
            }

            return result;
        }
        private static async Task ExecuteSubExpression(
            QueryContext context,
            CancellationToken cancellationToken,
            QueryExpressionVisitor visitor,
            IQueryExecutor executor,
            Expression expression)
        {
            // get element type
            Type elementType = null;
            var queryType = expression.Type.FindGenericType(typeof(IQueryable<>));
            if (queryType != null)
            {
                elementType = queryType.GetGenericArguments()[0];
            }

            var query = visitor.BaseQuery.Provider.CreateQuery(expression);
            var method = typeof(IQueryExecutor)
                .GetMethod("ExecuteQueryAsync")
                .MakeGenericMethod(elementType);
            var parameters = new object[]
            {
                context, query, cancellationToken
            };
            var task = method.Invoke(executor, parameters) as Task<QueryResult>;
            var result = await task;

            var any = result.Results.Cast<object>().Any();
            if (!any)
            {
                // Which means previous expression does not have result, and should throw ResourceNotFoundException.
                throw new ResourceNotFoundException(Resources.ResourceNotFound);
            }
        }
        private static async Task CheckSubExpressionResult(
            QueryContext context,
            CancellationToken cancellationToken,
            IEnumerable enumerableResult,
            QueryExpressionVisitor visitor,
            IQueryExecutor executor,
            Expression expression)
        {
            if (enumerableResult.GetEnumerator().MoveNext())
            {
                // If there is some result, will not have additional processing
                return;
            }

            var methodCallExpression = expression as MethodCallExpression;

            // This will remove unneeded statement which includes $expand, $select,$top,$skip,$orderby
            methodCallExpression = methodCallExpression.RemoveUnneededStatement();
            if (methodCallExpression == null || methodCallExpression.Arguments.Count != 2)
            {
                return;
            }

            if (methodCallExpression.Method.Name == ExpressionMethodNameOfWhere)
            {
                // Throw exception if key as last where statement, or remove $filter where statement
                methodCallExpression = CheckWhereCondition(methodCallExpression);
                if (methodCallExpression == null || methodCallExpression.Arguments.Count != 2)
                {
                    return;
                }

                // Call without $filter where statement and with Key where statement
                if (methodCallExpression.Method.Name == ExpressionMethodNameOfWhere)
                {
                    // The last where from $filter is removed and run with key where statement
                    await ExecuteSubExpression(context, cancellationToken, visitor, executor, methodCallExpression);
                    return;
                }
            }

            if (methodCallExpression.Method.Name != ExpressionMethodNameOfSelect
                && methodCallExpression.Method.Name != ExpressionMethodNameOfSelectMany)
            {
                // If last statement is not select property, will no further checking
                return;
            }

            var subExpression = methodCallExpression.Arguments[0];

            // Remove appended statement like Where(Param_0 => (Param_0.Prop != null)) if there is one
            subExpression = subExpression.RemoveAppendWhereStatement();

            await ExecuteSubExpression(context, cancellationToken, visitor, executor, subExpression);
        }
        /// <summary>
        /// Asynchronously executes the query flow.
        /// </summary>
        /// <param name="context">
        /// The query context.
        /// </param>
        /// <param name="cancellationToken">
        /// A cancellation token.
        /// </param>
        /// <returns>
        /// A task that represents the asynchronous
        /// operation whose result is a query result.
        /// </returns>
        public async Task<QueryResult> QueryAsync(
            QueryContext context,
            CancellationToken cancellationToken)
        {
            Ensure.NotNull(context, "context");

            // STEP 1: pre-filter
            var filters = context.GetHookPoints<IQueryFilter>();
            foreach (var filter in filters.Reverse())
            {
                await filter.FilterRequestAsync(context, cancellationToken);
                if (context.Result != null)
                {
                    return context.Result;
                }
            }

            // STEP 2: process query expression
            var expression = context.Request.Expression;
            var visitor = new QueryExpressionVisitor(context);
            expression = visitor.Visit(expression);

            // STEP 3: execute query
            QueryResult result = null;
            var executor = context.GetHookPoint<IQueryExecutor>();
            if (executor == null)
            {
                throw new NotSupportedException();
            }

            var queryType = expression.Type
                .FindGenericType(typeof(IQueryable<>));
            if (queryType != null)
            {
                var query = visitor.BaseQuery.Provider.CreateQuery(expression);
                var method = typeof(IQueryExecutor)
                    .GetMethod("ExecuteQueryAsync")
                    .MakeGenericMethod(queryType.GetGenericArguments()[0]);
                var parameters = new object[]
                {
                    context, query, cancellationToken
                };
                var task = method.Invoke(executor, parameters) as Task<QueryResult>;
                result = await task;
            }
            else
            {
                var method = typeof(IQueryExecutor)
                    .GetMethod("ExecuteSingleAsync")
                    .MakeGenericMethod(expression.Type);
                var parameters = new object[]
                {
                    context, visitor.BaseQuery, expression, cancellationToken
                };
                var task = method.Invoke(executor, parameters) as Task<QueryResult>;
                result = await task;
            }

            if (result != null)
            {
                result.ResultsSource = visitor.EntitySet;
            }

            context.Result = result;

            // STEP 4: post-filter
            foreach (var filter in filters)
            {
                await filter.FilterResultAsync(context, cancellationToken);
            }

            return context.Result;
        }