Search in sources :

Example 1 with QueryCompilerSyntaxException

use of org.datanucleus.store.query.compiler.QueryCompilerSyntaxException in project datanucleus-rdbms by datanucleus.

the class QueryToSQLMapper method compileFilter.

/**
 * Method to compile the WHERE clause of the query into the SQLStatement.
 */
protected void compileFilter() {
    if (compilation.getExprFilter() != null) {
        // Apply the filter to the SQLStatement
        compileComponent = CompilationComponent.FILTER;
        if (QueryUtils.expressionHasOrOperator(compilation.getExprFilter())) {
            compileProperties.put("Filter.OR", true);
        }
        if (QueryUtils.expressionHasNotOperator(compilation.getExprFilter())) {
            // Really we want to know if there is a NOT contains, but just check NOT for now
            compileProperties.put("Filter.NOT", true);
        }
        if (stmt instanceof SelectStatement && ((SelectStatement) stmt).getNumberOfUnions() > 0) {
            // Process each UNIONed statement separately TODO This is only necessary when the filter contains things like "instanceof"/"TYPE" so maybe detect that
            List<SelectStatement> unionStmts = ((SelectStatement) stmt).getUnions();
            // a). Main SelectStatement, disable unions while we process it
            SQLStatement originalStmt = stmt;
            ((SelectStatement) stmt).setAllowUnions(false);
            SQLExpression filterSqlExpr = (SQLExpression) compilation.getExprFilter().evaluate(this);
            if (!(filterSqlExpr instanceof BooleanExpression)) {
                throw new QueryCompilerSyntaxException("Filter compiles to something that is not a boolean expression. Kindly fix your query : " + filterSqlExpr);
            }
            BooleanExpression filterExpr = getBooleanExpressionForUseInFilter((BooleanExpression) filterSqlExpr);
            stmt.whereAnd(filterExpr, true);
            ((SelectStatement) stmt).setAllowUnions(true);
            // b). UNIONed SelectStatements
            for (SelectStatement unionStmt : unionStmts) {
                stmt = unionStmt;
                stmt.setQueryGenerator(this);
                filterSqlExpr = (SQLExpression) compilation.getExprFilter().evaluate(this);
                if (!(filterSqlExpr instanceof BooleanExpression)) {
                    throw new QueryCompilerSyntaxException("Filter compiles to something that is not a boolean expression. Kindly fix your query : " + filterSqlExpr);
                }
                filterExpr = getBooleanExpressionForUseInFilter((BooleanExpression) filterSqlExpr);
                stmt.whereAnd(filterExpr, true);
                stmt.setQueryGenerator(null);
            }
            stmt = originalStmt;
        } else {
            SQLExpression filterSqlExpr = (SQLExpression) compilation.getExprFilter().evaluate(this);
            if (!(filterSqlExpr instanceof BooleanExpression)) {
                throw new QueryCompilerSyntaxException("Filter compiles to something that is not a boolean expression. Kindly fix your query : " + filterSqlExpr);
            }
            BooleanExpression filterExpr = (BooleanExpression) filterSqlExpr;
            filterExpr = getBooleanExpressionForUseInFilter(filterExpr);
            stmt.whereAnd(filterExpr, true);
        }
        compileComponent = null;
    }
}
Also used : SelectStatement(org.datanucleus.store.rdbms.sql.SelectStatement) BooleanExpression(org.datanucleus.store.rdbms.sql.expression.BooleanExpression) SQLExpression(org.datanucleus.store.rdbms.sql.expression.SQLExpression) QueryCompilerSyntaxException(org.datanucleus.store.query.compiler.QueryCompilerSyntaxException) SQLStatement(org.datanucleus.store.rdbms.sql.SQLStatement)

Example 2 with QueryCompilerSyntaxException

use of org.datanucleus.store.query.compiler.QueryCompilerSyntaxException in project datanucleus-rdbms by datanucleus.

the class QueryToSQLMapper method processCaseExpression.

protected Object processCaseExpression(CaseExpression expr, SQLExpression typeExpr) {
    processingCase = true;
    try {
        List<ExpressionPair> conditions = expr.getConditions();
        Iterator<ExpressionPair> whenExprIter = conditions.iterator();
        SQLExpression[] whenSqlExprs = new SQLExpression[conditions.size()];
        SQLExpression[] actionSqlExprs = new SQLExpression[conditions.size()];
        boolean numericCase = false;
        boolean booleanCase = false;
        boolean stringCase = false;
        boolean typeSet = false;
        if (typeExpr != null) {
            if (typeExpr instanceof NumericExpression) {
                numericCase = true;
                typeSet = true;
            } else if (typeExpr instanceof BooleanExpression) {
                booleanCase = true;
                typeSet = true;
            } else if (typeExpr instanceof StringExpression) {
                stringCase = true;
                typeSet = true;
            }
        }
        int i = 0;
        while (whenExprIter.hasNext()) {
            ExpressionPair pair = whenExprIter.next();
            Expression whenExpr = pair.getWhenExpression();
            whenExpr.evaluate(this);
            whenSqlExprs[i] = stack.pop();
            if (!(whenSqlExprs[i] instanceof BooleanExpression)) {
                throw new QueryCompilerSyntaxException("IF/ELSE conditional expression should return boolean but doesn't : " + expr);
            }
            Expression actionExpr = pair.getActionExpression();
            actionExpr.evaluate(this);
            actionSqlExprs[i] = stack.pop();
            if (!typeSet) {
                if (actionSqlExprs[i] instanceof NumericExpression) {
                    numericCase = true;
                    typeSet = true;
                } else if (actionSqlExprs[i] instanceof BooleanExpression) {
                    booleanCase = true;
                    typeSet = true;
                } else if (actionSqlExprs[i] instanceof StringExpression) {
                    stringCase = true;
                    typeSet = true;
                }
            }
            i++;
        }
        Expression elseExpr = expr.getElseExpression();
        elseExpr.evaluate(this);
        SQLExpression elseActionSqlExpr = stack.pop();
        // Check that all action sql expressions are consistent
        for (int j = 1; j < actionSqlExprs.length; j++) {
            if (!checkCaseExpressionsConsistent(actionSqlExprs[0], actionSqlExprs[j])) {
                throw new QueryCompilerSyntaxException("IF/ELSE action expression " + actionSqlExprs[j] + " is of different type to first action " + actionSqlExprs[0] + " - must be consistent");
            }
        }
        if (!checkCaseExpressionsConsistent(actionSqlExprs[0], elseActionSqlExpr)) {
            throw new QueryCompilerSyntaxException("IF/ELSE action expression " + elseActionSqlExpr + " is of different type to first action " + actionSqlExprs[0] + " - must be consistent");
        }
        SQLExpression caseSqlExpr = null;
        if (numericCase) {
            caseSqlExpr = new org.datanucleus.store.rdbms.sql.expression.CaseNumericExpression(whenSqlExprs, actionSqlExprs, elseActionSqlExpr);
        } else if (booleanCase) {
            caseSqlExpr = new org.datanucleus.store.rdbms.sql.expression.CaseBooleanExpression(whenSqlExprs, actionSqlExprs, elseActionSqlExpr);
        } else if (stringCase) {
            caseSqlExpr = new org.datanucleus.store.rdbms.sql.expression.CaseStringExpression(whenSqlExprs, actionSqlExprs, elseActionSqlExpr);
        } else {
            caseSqlExpr = new org.datanucleus.store.rdbms.sql.expression.CaseExpression(whenSqlExprs, actionSqlExprs, elseActionSqlExpr);
        }
        stack.push(caseSqlExpr);
        return caseSqlExpr;
    } finally {
        processingCase = false;
    }
}
Also used : SQLExpression(org.datanucleus.store.rdbms.sql.expression.SQLExpression) NumericExpression(org.datanucleus.store.rdbms.sql.expression.NumericExpression) BooleanExpression(org.datanucleus.store.rdbms.sql.expression.BooleanExpression) PrimaryExpression(org.datanucleus.store.query.expression.PrimaryExpression) BooleanSubqueryExpression(org.datanucleus.store.rdbms.sql.expression.BooleanSubqueryExpression) TypeConverterExpression(org.datanucleus.store.rdbms.sql.expression.TypeConverterExpression) StringExpression(org.datanucleus.store.rdbms.sql.expression.StringExpression) NumericSubqueryExpression(org.datanucleus.store.rdbms.sql.expression.NumericSubqueryExpression) StringSubqueryExpression(org.datanucleus.store.rdbms.sql.expression.StringSubqueryExpression) MapExpression(org.datanucleus.store.rdbms.sql.expression.MapExpression) ArrayExpression(org.datanucleus.store.query.expression.ArrayExpression) CaseExpression(org.datanucleus.store.query.expression.CaseExpression) CreatorExpression(org.datanucleus.store.query.expression.CreatorExpression) NewObjectExpression(org.datanucleus.store.rdbms.sql.expression.NewObjectExpression) OrderExpression(org.datanucleus.store.query.expression.OrderExpression) TemporalSubqueryExpression(org.datanucleus.store.rdbms.sql.expression.TemporalSubqueryExpression) ClassExpression(org.datanucleus.store.query.expression.ClassExpression) BooleanExpression(org.datanucleus.store.rdbms.sql.expression.BooleanExpression) Expression(org.datanucleus.store.query.expression.Expression) SQLExpression(org.datanucleus.store.rdbms.sql.expression.SQLExpression) ParameterExpression(org.datanucleus.store.query.expression.ParameterExpression) UnboundExpression(org.datanucleus.store.rdbms.sql.expression.UnboundExpression) TemporalExpression(org.datanucleus.store.rdbms.sql.expression.TemporalExpression) SubqueryExpression(org.datanucleus.store.query.expression.SubqueryExpression) ResultAliasExpression(org.datanucleus.store.rdbms.sql.expression.ResultAliasExpression) VariableExpression(org.datanucleus.store.query.expression.VariableExpression) TypeExpression(org.datanucleus.store.query.expression.TypeExpression) NumericExpression(org.datanucleus.store.rdbms.sql.expression.NumericExpression) CollectionExpression(org.datanucleus.store.rdbms.sql.expression.CollectionExpression) DyadicExpression(org.datanucleus.store.query.expression.DyadicExpression) ColumnExpression(org.datanucleus.store.rdbms.sql.expression.ColumnExpression) JoinExpression(org.datanucleus.store.query.expression.JoinExpression) InvokeExpression(org.datanucleus.store.query.expression.InvokeExpression) StringExpression(org.datanucleus.store.rdbms.sql.expression.StringExpression) QueryCompilerSyntaxException(org.datanucleus.store.query.compiler.QueryCompilerSyntaxException) ExpressionPair(org.datanucleus.store.query.expression.CaseExpression.ExpressionPair)

Example 3 with QueryCompilerSyntaxException

use of org.datanucleus.store.query.compiler.QueryCompilerSyntaxException in project datanucleus-rdbms by datanucleus.

the class QueryToSQLMapper method processParameterExpression.

/**
 * Method to process a parameter expression.
 * The optional argument controls whether we should create this as a parameter or as a literal (i.e the param value is known etc).
 * If the parameter doesn't have its value defined then returns ParameterLiteral otherwise we get an XXXLiteral of the (declared) type of the parameter
 * @param expr The ParameterExpression
 * @param asLiteral Whether to create a SQLLiteral rather than a parameter literal
 * @return The processed expression
 */
protected Object processParameterExpression(ParameterExpression expr, boolean asLiteral) {
    if (compileComponent == CompilationComponent.ORDERING || compileComponent == CompilationComponent.RESULT) {
        // All JDBC drivers I know don't allow parameters in the order-by, or update clause
        // Note that we also don't allow parameters in result clause since SQLStatement squashes all SELECT expression to a String so losing info about params
        asLiteral = true;
    } else if (compileComponent == CompilationComponent.UPDATE && processingCase && !storeMgr.getDatastoreAdapter().supportsOption(DatastoreAdapter.PARAMETER_IN_CASE_IN_UPDATE_CLAUSE)) {
        // This database doesn't support parameters within a CASE expression in the UPDATE clause, so process as a literal
        asLiteral = true;
    }
    if (expr.getPosition() >= 0) {
        if (paramNameByPosition == null) {
            paramNameByPosition = new HashMap<>();
        }
        paramNameByPosition.put(Integer.valueOf(expr.getPosition()), expr.getId());
    }
    // Find the parameter value if supplied
    Object paramValue = null;
    boolean paramValueSet = false;
    if (parameters != null && parameters.size() > 0) {
        // Check if the parameter has a value
        if (parameters.containsKey(expr.getId())) {
            // Named parameter
            paramValue = parameters.get(expr.getId());
            paramValueSet = true;
        } else if (parameterValueByName != null && parameterValueByName.containsKey(expr.getId())) {
            // Positional parameter, but already encountered
            paramValue = parameterValueByName.get(expr.getId());
            paramValueSet = true;
        } else {
            // Positional parameter, not yet encountered
            int position = positionalParamNumber;
            if (positionalParamNumber < 0) {
                position = 0;
            }
            if (parameters.containsKey(Integer.valueOf(position))) {
                paramValue = parameters.get(Integer.valueOf(position));
                paramValueSet = true;
                positionalParamNumber = position + 1;
                if (parameterValueByName == null) {
                    parameterValueByName = new HashMap<>();
                }
                parameterValueByName.put(expr.getId(), paramValue);
            }
        }
    }
    // Find the type to use for the parameter
    JavaTypeMapping m = paramMappingForName.get(expr.getId());
    if (m == null) {
        // Try to determine from provided parameter value or from symbol table (declared type)
        if (paramValue != null) {
            if (!storeMgr.getMetaDataManager().isClassPersistable(paramValue.getClass().getName()) && !paramValue.getClass().isArray() && !paramValue.getClass().isInterface() && !Collection.class.isAssignableFrom(paramValue.getClass()) && !Map.class.isAssignableFrom(paramValue.getClass()) && !storeMgr.getNucleusContext().getTypeManager().isSupportedSecondClassType(paramValue.getClass().getName())) {
                // Test for this being the "id" of a persistable object
                // Persistable/array/interface/collection/map/simple cannot be an object "id"
                String className = storeMgr.getClassNameForObjectID(paramValue, clr, ec);
                if (className != null) {
                    // Identity for persistable class
                    AbstractClassMetaData cmd = storeMgr.getMetaDataManager().getMetaDataForClass(className, clr);
                    if (cmd.getIdentityType() == IdentityType.APPLICATION) {
                        Class cls = clr.classForName(className);
                        m = exprFactory.getMappingForType(cls, false);
                        m = new PersistableIdMapping((PersistableMapping) m);
                    }
                }
            }
            if (m == null) {
                // Use the type of the input parameter value
                try {
                    m = exprFactory.getMappingForType(paramValue.getClass(), false);
                    if (m instanceof TypeConverterMapping && expr.getSymbol().getValueType() != null && expr.getSymbol().getValueType() != m.getJavaType()) {
                        // This is because if we have a parameter of type "ZoneInfo" it needs to use TimeZone since we have the TypeConverter for that
                        try {
                            m = exprFactory.getMappingForType(expr.getSymbol().getValueType(), false);
                        } catch (NucleusUserException nue) {
                        }
                    }
                } catch (NucleusUserException nue) {
                    // Maybe it needs a TypeConverter so try with the (declared) symbol type of this parameter
                    m = exprFactory.getMappingForType(expr.getSymbol().getValueType(), false);
                }
            }
            if (expr.getSymbol() != null && expr.getSymbol().getValueType() != null) {
                if (!QueryUtils.queryParameterTypesAreCompatible(expr.getSymbol().getValueType(), paramValue.getClass())) {
                    throw new QueryCompilerSyntaxException(Localiser.msg("021118", expr.getId(), expr.getSymbol().getValueType().getName(), paramValue.getClass().getName()));
                }
                if (expr.getSymbol().getValueType() != paramValue.getClass()) {
                    // Mark as not precompilable since the supplied type implies a subclass of the declared type
                    setNotPrecompilable();
                }
            }
        } else if (expr.getSymbol() != null && expr.getSymbol().getValueType() != null) {
            Class valueType = expr.getSymbol().getValueType();
            if (!paramValueSet) {
                if (valueType.isInterface()) {
                    // Special case where we have an interface parameter (not set), and don't know the type, so we pick the first implementation just to get something that works
                    // This is recompiled when the parameter is provided so is just for use in "compile()"
                    String[] implNames = storeMgr.getMetaDataManager().getClassesImplementingInterface(valueType.getName(), clr);
                    if (implNames != null && implNames.length > 0) {
                        valueType = clr.classForName(implNames[0]);
                        setNotPrecompilable();
                    }
                }
            }
            // Use the declared type of the parameter (explicit params)
            m = exprFactory.getMappingForType(valueType, false);
        }
    }
    if (asLiteral && m != null && !m.representableAsStringLiteralInStatement()) {
        // Must keep this as a parameter since its String form is no good in statements
        asLiteral = false;
    }
    if (asLiteral) {
        // Parameter being represented as a literal (for whatever reason), so no longer precompilable
        if (isPrecompilable()) {
            NucleusLogger.QUERY.debug("Parameter " + expr + " is being resolved as a literal, so the query is no longer precompilable");
        }
        setNotPrecompilable();
    } else if (paramValue == null && expr.getSymbol() != null) {
        if (isPrecompilable()) {
            NucleusLogger.QUERY.debug("Parameter " + expr + " is set to null so this has to be resolved as a NullLiteral, and the query is no longer precompilable");
        }
        setNotPrecompilable();
    }
    // Create the SQLExpression for this parameter, either as value-literal or as parameter-literal
    SQLExpression sqlExpr = null;
    if (paramValueSet && paramValue == null && options.contains(OPTION_NULL_PARAM_USE_IS_NULL)) {
        // Value is set to null, but we enforce a NullLiteral for the case of null comparisons e.g we don't want "field = ?", but instead "field IS NULL"
        sqlExpr = exprFactory.newLiteral(stmt, null, null);
    } else if (asLiteral) {
        // Create a value-literal as requested
        sqlExpr = exprFactory.newLiteral(stmt, m, paramValue);
    } else {
        // Create a parameter-literal with it tied to the parameter name for later replacement in the statement
        sqlExpr = exprFactory.newLiteralParameter(stmt, m, paramValue, expr.getId());
        if (sqlExpr instanceof ParameterLiteral) {
            ((ParameterLiteral) sqlExpr).setName(expr.getId());
        }
        if (expressionForParameter == null) {
            expressionForParameter = new HashMap<>();
        }
        expressionForParameter.put(expr.getId(), sqlExpr);
        paramMappingForName.put(expr.getId(), m);
    }
    stack.push(sqlExpr);
    return sqlExpr;
}
Also used : ParameterLiteral(org.datanucleus.store.rdbms.sql.expression.ParameterLiteral) SQLExpression(org.datanucleus.store.rdbms.sql.expression.SQLExpression) HashMap(java.util.HashMap) JavaTypeMapping(org.datanucleus.store.rdbms.mapping.java.JavaTypeMapping) NucleusUserException(org.datanucleus.exceptions.NucleusUserException) PersistableIdMapping(org.datanucleus.store.rdbms.mapping.java.PersistableIdMapping) AbstractClassMetaData(org.datanucleus.metadata.AbstractClassMetaData) PersistableMapping(org.datanucleus.store.rdbms.mapping.java.PersistableMapping) TypeConverterMapping(org.datanucleus.store.rdbms.mapping.java.TypeConverterMapping) QueryCompilerSyntaxException(org.datanucleus.store.query.compiler.QueryCompilerSyntaxException) DatastoreClass(org.datanucleus.store.rdbms.table.DatastoreClass) FetchPlanForClass(org.datanucleus.FetchPlanForClass) Map(java.util.Map) HashMap(java.util.HashMap)

Example 4 with QueryCompilerSyntaxException

use of org.datanucleus.store.query.compiler.QueryCompilerSyntaxException in project datanucleus-rdbms by datanucleus.

the class QueryToSQLMapper method compile.

/**
 * Method to update the supplied SQLStatement with the components of the specified query.
 * During the compilation process this updates the SQLStatement "compileComponent" to the
 * component of the query being compiled.
 */
public void compile() {
    if (NucleusLogger.QUERY.isDebugEnabled() && parentMapper == null) {
        // Give debug output of compilation
        StringBuilder str = new StringBuilder("JoinType : navigation(default=");
        str.append(defaultJoinType != null ? defaultJoinType : "(using nullability)");
        str.append(", filter=");
        str.append(defaultJoinTypeFilter != null ? defaultJoinTypeFilter : "(using nullability)");
        str.append(")");
        if (extensionsByName != null) {
            Iterator<Map.Entry<String, Object>> extensionsIter = extensionsByName.entrySet().iterator();
            while (extensionsIter.hasNext()) {
                Map.Entry<String, Object> entry = extensionsIter.next();
                String key = entry.getKey();
                if (key.startsWith("datanucleus.query.jdoql.") && key.endsWith(".join")) {
                    // Alias join definition
                    String alias = key.substring("datanucleus.query.jdoql.".length(), key.lastIndexOf(".join"));
                    str.append(", ").append(alias).append("=").append(entry.getValue());
                }
            }
        }
        NucleusLogger.QUERY.debug("Compile of " + compilation.getQueryLanguage() + " into SQL - " + str);
    }
    compileFrom();
    compileFilter();
    if (stmt instanceof UpdateStatement) {
        compileUpdate((UpdateStatement) stmt);
    } else if (stmt instanceof SelectStatement) {
        SelectStatement selectStmt = (SelectStatement) stmt;
        // the datastore doesn't allow select of some field types when used with DISTINCT
        if (compilation.getResultDistinct()) {
            selectStmt.setDistinct(true);
        } else if (!options.contains(OPTION_EXPLICIT_JOINS) && compilation.getExprResult() == null) {
            // Joins are made implicitly and no result so set distinct based on whether joining to other table groups
            if (selectStmt.getNumberOfTableGroups() > 1) {
                // Queries against an extent always consider only distinct candidate instances, regardless of whether distinct is specified (JDO spec)
                if (!options.contains(OPTION_NON_DISTINCT_IMPLICIT_JOINS)) {
                    // If user can guarantee distinct w/ query no reason to take performance hit of distinct clause
                    selectStmt.setDistinct(true);
                }
            }
        }
        compileResult(selectStmt);
        compileGrouping(selectStmt);
        compileHaving(selectStmt);
        compileOrdering(selectStmt);
    }
    // Check for variables that haven't been bound to the query (declared but not used)
    for (String symbol : compilation.getSymbolTable().getSymbolNames()) {
        Symbol sym = compilation.getSymbolTable().getSymbol(symbol);
        if (sym.getType() == Symbol.VARIABLE) {
            if (compilation.getCompilationForSubquery(sym.getQualifiedName()) == null && !hasSQLTableMappingForAlias(sym.getQualifiedName())) {
                // Variable not a subquery, nor had its table allocated
                throw new QueryCompilerSyntaxException("Query has variable \"" + sym.getQualifiedName() + "\" which is not bound to the query");
            }
        }
    }
}
Also used : SelectStatement(org.datanucleus.store.rdbms.sql.SelectStatement) UpdateStatement(org.datanucleus.store.rdbms.sql.UpdateStatement) Symbol(org.datanucleus.store.query.compiler.Symbol) QueryCompilerSyntaxException(org.datanucleus.store.query.compiler.QueryCompilerSyntaxException) Map(java.util.Map) HashMap(java.util.HashMap)

Aggregations

QueryCompilerSyntaxException (org.datanucleus.store.query.compiler.QueryCompilerSyntaxException)4 SQLExpression (org.datanucleus.store.rdbms.sql.expression.SQLExpression)3 HashMap (java.util.HashMap)2 Map (java.util.Map)2 SelectStatement (org.datanucleus.store.rdbms.sql.SelectStatement)2 BooleanExpression (org.datanucleus.store.rdbms.sql.expression.BooleanExpression)2 FetchPlanForClass (org.datanucleus.FetchPlanForClass)1 NucleusUserException (org.datanucleus.exceptions.NucleusUserException)1 AbstractClassMetaData (org.datanucleus.metadata.AbstractClassMetaData)1 Symbol (org.datanucleus.store.query.compiler.Symbol)1 ArrayExpression (org.datanucleus.store.query.expression.ArrayExpression)1 CaseExpression (org.datanucleus.store.query.expression.CaseExpression)1 ExpressionPair (org.datanucleus.store.query.expression.CaseExpression.ExpressionPair)1 ClassExpression (org.datanucleus.store.query.expression.ClassExpression)1 CreatorExpression (org.datanucleus.store.query.expression.CreatorExpression)1 DyadicExpression (org.datanucleus.store.query.expression.DyadicExpression)1 Expression (org.datanucleus.store.query.expression.Expression)1 InvokeExpression (org.datanucleus.store.query.expression.InvokeExpression)1 JoinExpression (org.datanucleus.store.query.expression.JoinExpression)1 OrderExpression (org.datanucleus.store.query.expression.OrderExpression)1