2011-10-18 19 views

Respuesta

2

Doubt it. La API está presente en todos los RDBMS y proporciona ciertas construcciones como "LIKE"/"SUBSTRING" que podrían asignarse a algo de esa forma cuando se usan en Oracle para una columna TEXT, pero de nuevo pueden usar estándar SQL. No existe una manera estándar de insistir en eso

+1

Así que supongo que tengo que recurrir a una consulta nativa de JPA mediante la concatenación de cadenas y olvidar la seguridad del tipo. Ay. – Ryan

2

Acabo de escribir un OracleTextDictionary para openjpa, que convierte operadores comunes 'similares' en operadores 'contains', cuando el argumento está prefijado con un marcador "mágico".

De esta manera, es posible utilizar QueryDSL o Criteria Language (o JPQL) con texto de Oracle.

El diccionario detecta declaraciones LIKE con un marcador mágico en el argumento, y reescribe el SQL para usar una llamada CTX CONTAINS.

Una desventaja es que el puntaje no es accesible de una manera simple, pero sería posible mejorar el controlador para ordenar por puntaje. Siéntase libre de editar el código :-)

Supongo que es posible migrar a puerto, suponiendo que hay un mecanismo similar para ajustar las consultas de la base de datos a un db específico.

package se.grynna.dict; 

import java.util.regex.Matcher; 
import java.util.regex.Pattern; 

import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration; 
import org.apache.openjpa.jdbc.sql.OracleDictionary; 
import org.apache.openjpa.jdbc.sql.SQLBuffer; 
import org.apache.openjpa.jdbc.sql.Select; 

public class OracleTextDictionary extends OracleDictionary { 

    public static final String CTX_MAGIC_MARKER = "@[email protected]"; 
    final static Pattern likePattern = Pattern 
     .compile("t(\\d+)\\.(\\S+) LIKE (\\?)"); 


    @Override 
    protected SQLBuffer toSelect(SQLBuffer select, 
     JDBCFetchConfiguration fetch, SQLBuffer tables, SQLBuffer where, 
     SQLBuffer group, SQLBuffer having, SQLBuffer order, 
     boolean distinct, boolean forUpdate, long start, long end,Select sel) { 

     SQLBuffer sqlBuffer = super.toSelect(select, fetch, tables, where, 
      group, having, order, distinct, forUpdate, start, end, sel); 

     SQLBuffer tmpBuf = sqlBuffer; 

     String sql = tmpBuf.getSQL(); 

     int label = 1; 

     for (Matcher m = likePattern.matcher(sql); m.find(); sql = tmpBuf.getSQL()) { 


     int argPos = m.start(3); 
     int argIdx = findArgIdx(sql, argPos); 
     Object o = tmpBuf.getParameters().get(argIdx); 
     if(o == null) break; 
     String arg = o.toString(); 

     if (arg.startsWith(CTX_MAGIC_MARKER)) { 

      if (tmpBuf == sqlBuffer) { 
       tmpBuf = new SQLBuffer(sqlBuffer); 
      } 


     arg = arg.substring(CTX_MAGIC_MARKER.length()); 
     setParameter(tmpBuf, argIdx, arg); 

     String aliasNo = m.group(1); 
     String colName = m.group(2); 

     } 

     String replace = String.format("(CONTAINS(t%s.%s,?,%d)>0)", 
        aliasNo, colName, label++); 
     tmpBuf.replaceSqlString(m.start(), m.end(), replace); 
       m.reset(tmpBuf.getSQL()); 
     } 

     } 

    return tmpBuf; 
    } 

    @SuppressWarnings("unchecked") 
    private void setParameter(SQLBuffer tmpBuf, int argIdx, String arg) { 
     tmpBuf.getParameters().set(argIdx, arg); 

    } 

    private int findArgIdx(String sql, int argPos) { 
     int count = -1; 
     for (int i = 0; i <= argPos; i++) { 
      char c = sql.charAt(i); 
      if (c == '?') { 
       count++; 
      } 
     } 
     return count; 
    } 



} 

Ejemplo: El siguiente entrada (obviamente artificial) produce se llama con los parámetros:

:1 "@[email protected] near ponies" 
:2 "@[email protected]" 
:3 "@[email protected]%" 
:4 "abc1%"      <-- an ordinary like :-) 
:5 "@[email protected]%" 

JPQL

select distinct customer 
from Customer customer 
where customer.custName like :a1 and customer.custName like :a2 and customer.custName like :a1 and customer.custId in (select d.custId 
from Customer d 
where d.custName like :a3 or d.custName like :a1) 

SQL

SELECT t0.custId, 
    t0.custName 
FROM Customer t0 
WHERE ((CONTAINS(t0.custName,?,1)>1) 
AND (CONTAINS(t0.custName,?,2) >1) 
AND (CONTAINS(t0.custName,?,3) >1) 
AND t0.custId     IN 
    (SELECT t1.custId 
    FROM Customer t1 
    WHERE (t1.custName LIKE ?    <---- the like survives.... 
    OR (CONTAINS(t1.custName,?,1)>1)) 
)) 
AND ROWNUM <= ? 

Como una nota : QueryDsl en realidad lo hace tener un operador'contains ', supuestamente para el backend Lucene, para el cual los backends jpa y sql generan una declaración' like '.

No he encontrado una forma de sobrecargar el operador contains, para que pueda ser utilizado. (Aparte de reescribir el código, que no puedo hacer porque estoy usando la versión incluida con WebSphere.)

Por lo tanto, recurro a un pequeño método estático para que se vea bien cuando se utiliza QuertyDSL.

// x.where(c.custName.like(CTX.contains("omg near ponies")))); 

Sería aún mejor si JPQL podría proporcionar algunas abstracciones (o plugins) para los motores de búsqueda de texto completo ...

10

Criterios soporta una API() la función que permite a una función de base de datos para ser llamado por nombre.

qb.gt(qb.function("CONTAINS", root.get("name"), qb.parameter("name"), qb.literal(1)), 1) 

EclipseLink también lo admite en JPQL utilizando la palabra clave FUNC.

+0

Esto parece prometedor. No lo he intentado sin embargo. – Ryan

+0

Eso me da error de SQL: ORA-29909: etiqueta para el operador auxiliar no es un número literal. ¿Algunas ideas? – Tomasz

+0

Lo encontré. query.setHint (QueryHints.BIND_PARAMETERS, HintValues.FALSE); – Tomasz