From 7991a33f130eab85395f57834bf4d7ad90b2a9a5 Mon Sep 17 00:00:00 2001 From: pvarga88 Date: Wed, 15 Nov 2023 09:57:22 -0500 Subject: [PATCH] Allow escaping of '?' using '??' in SQL expression (#1935) Allow escaping of '?' using '??' in SQL expression and testEscapedQuestionMarkInSQLOperator to test ?? in SQL operator --- .../persistence/expressions/Expression.java | 13 ++++++++ .../jpa/jpql/ExpressionBuilderVisitor.java | 2 ++ .../persistence/jpa/json/TestJson.java | 31 ++++++++++++++++++- 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/expressions/Expression.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/expressions/Expression.java index ca57a653b87..ff443fa38de 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/expressions/Expression.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/expressions/Expression.java @@ -1981,6 +1981,11 @@ public Expression sql(String sql, List arguments) { int start = 0; int index = sql.indexOf('?'); while (index != -1) { + // ? can be escaped as ?? to support ? as a native SQL operator (e.g. on Postgres) + if (index < sql.length() - 1 && sql.charAt(index + 1) == '?') { + index = sql.indexOf('?', index + 2); + continue; + } v.add(sql.substring(start, index)); start = index + 1; index = sql.indexOf('?', start); @@ -1989,6 +1994,14 @@ public Expression sql(String sql, List arguments) { v.add(sql.substring(start, sql.length())); } anOperator.printsAs(v); + //Postgres expects '??' as an escape mechanism for '?' in parameterized queries + //https://jdbc.postgresql.org/documentation/query/#using-the-statement-or-preparedstatement-interface + //On other platforms, replace ?? with ? in code which is passed as a part of SQL into DB + if (getSession() == null || !getSession().getPlatform().isPostgreSQL()) { + for (int i = 0; i < anOperator.getDatabaseStrings().length; i++) { + anOperator.getDatabaseStrings()[i] = anOperator.getDatabaseStrings()[i].replace("??", "?"); + } + } anOperator.bePrefix(); anOperator.setNodeClass(ClassConstants.FunctionExpression_Class); diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/ExpressionBuilderVisitor.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/ExpressionBuilderVisitor.java index c8b9e9fca47..88564f8987e 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/ExpressionBuilderVisitor.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/jpa/jpql/ExpressionBuilderVisitor.java @@ -1068,6 +1068,8 @@ public void visit(FunctionExpression expression) { } queryExpression = queryExpressions.remove(0); + // ensure the session is set for the 'SQL' operator + queryExpression.getBuilder().setSession(queryContext.getSession()); // SQL if (identifier == org.eclipse.persistence.jpa.jpql.parser.Expression.SQL) { diff --git a/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/json/TestJson.java b/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/json/TestJson.java index dbc6e181f29..1d2cd494c8d 100644 --- a/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/json/TestJson.java +++ b/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/json/TestJson.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -30,6 +30,7 @@ import org.eclipse.persistence.jpa.test.framework.Emf; import org.eclipse.persistence.jpa.test.framework.EmfRunner; import org.eclipse.persistence.jpa.test.framework.Property; +import org.eclipse.persistence.sessions.Session; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -254,4 +255,32 @@ public void testSelectJsonInWhereCondition() { } } + @Test + public void testEscapedQuestionMarkInSQLOperator() { + EntityManager em = emf.createEntityManager(); + + if (emf.unwrap(Session.class).getPlatform().isOracle()) { + JsonValue value = Json.createObjectBuilder() + .add("id", "1007") + .build(); + try { + em.getTransaction().begin(); + JsonEntity e = new JsonEntity(1007, value); + em.persist(e); + em.flush(); + em.getTransaction().commit(); + em.clear(); + + JsonEntity dbValue = em.createQuery( + "SELECT v FROM JsonEntity v WHERE SQL('JSON_EXISTS(?, ''$??(@.id == 1007)'')', v.value)", JsonEntity.class) + .getSingleResult(); + Assert.assertEquals(value, dbValue.getValue()); + } finally { + if (em.getTransaction().isActive()) { + em.getTransaction().rollback(); + } + em.close(); + } + } + } }