001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.beanquery.sql; 018 019import org.apache.juneau.commons.beanquery.*; 020 021/** 022 * The SQL-generation strategy for a SQL {@link BeanQueryContext}: it quotes identifiers, renders each built-in 023 * {@code $}-operator into a dialect-correct predicate, and renders pagination. 024 * 025 * <p> 026 * The shared JDBC context (the {@code juneau-beanquery-sql} module) is dialect-agnostic plumbing: it walks a parsed 027 * search tree, delegates every leaf to the dialect, and combines leaves with {@code AND}/{@code OR}/{@code NOT} itself. 028 * All dialect-specific SQL text — identifier quoting, the built-in operator predicates, the {@code LIMIT}/ 029 * {@code OFFSET} clause — lives here. A custom operator instead carries its own {@link SearchSqlRenderer} 030 * (attached with {@link org.apache.juneau.commons.beanquery.SearchOperator.Builder#extension(Class, Object) SearchOperator.Builder.extension}); the 031 * context calls that renderer, passing this dialect so it can branch on {@link #id()}. 032 * 033 * <p> 034 * Juneau ships exactly one dialect today (Postgres, in {@code juneau-beanquery-postgres}). A dialect returns 035 * <jk>null</jk> from {@link #builtinRenderer(String)} for any built-in it cannot express (for example {@code $regex} on a 036 * database without regex support), and the context then rejects the query with 037 * {@link org.apache.juneau.commons.beanquery.BeanQuerySyntaxException.Code#BAD_VALUE BAD_VALUE} rather than emitting SQL that differs from the in-memory 038 * result. 039 * 040 * <h5 class='section'>Example:</h5> 041 * <p class='bjava'> 042 * SqlDialect <jv>dialect</jv> = PostgresDialect.<jsf>INSTANCE</jsf>; 043 * SqlFragment <jv>frag</jv> = <jv>dialect</jv>.renderBare(<jv>dialect</jv>.quote(<js>"status"</js>), SearchType.<jsf>ENUM</jsf>, <js>"OPEN"</js>, <jk>false</jk>); 044 * <jc>// frag.sql() == "lower(\"status\"::text) = lower(?)", frag.binds() == ["OPEN"]</jc> 045 * 046 * <jc>// A NUMERIC column receives the already-typed value, not the raw token.</jc> 047 * SqlFragment <jv>age</jv> = <jv>dialect</jv>.renderBare(<jv>dialect</jv>.quote(<js>"age"</js>), SearchType.<jsf>NUMERIC</jsf>, <jk>new</jk> BigDecimal(<js>"30"</js>), <jk>false</jk>); 048 * <jc>// age.sql() == "\"age\" = ?", age.binds() == [30]</jc> 049 * </p> 050 * 051 * @since 10.0.0 052 */ 053public interface SqlDialect { 054 055 /** 056 * The stable lowercase dialect id (for example {@code "postgres"}). 057 * 058 * @return The dialect id, never <jk>null</jk> or blank. 059 */ 060 String id(); 061 062 /** 063 * Quotes a column or table identifier for this dialect (for example {@code age} → {@code "age"}). 064 * 065 * @param identifier The raw identifier. Must not be <jk>null</jk>. 066 * @return The quoted identifier. 067 */ 068 String quote(String identifier); 069 070 /** 071 * Renders a bare pattern term (design ยง5.1) — text with optional {@code *}/{@code ?} wildcards, a version 072 * prefix, or an exact value for the other types — into a predicate. 073 * 074 * @param columnSql The dialect-quoted column reference. Must not be <jk>null</jk>. 075 * @param type The column's value type. Must not be <jk>null</jk>. 076 * @param value The literal's already-typed value ({@link SearchExpression#typedValue()}): a 077 * {@link java.math.BigDecimal} for {@code NUMERIC}, a {@link Boolean} for {@code BOOLEAN}, an 078 * {@link java.time.OffsetDateTime}, {@link java.time.LocalDateTime} or {@link java.time.LocalDate} for 079 * {@code TIMESTAMP}, and the already-{@code $$}-decoded {@link String} for {@code TEXT}, {@code ID}, {@code ENUM} 080 * and {@code VERSION}. Must not be <jk>null</jk>. 081 * @param quoted Whether the literal was quoted in the search string; a quoted literal is never given 082 * wildcard interpretation, even if it contains {@code *} or {@code ?}. 083 * @return The predicate fragment. 084 */ 085 SqlFragment renderBare(String columnSql, SearchType type, Object value, boolean quoted); 086 087 /** 088 * The renderer for a built-in leaf operator (for example {@code "$eq"}), or <jk>null</jk> if this dialect cannot 089 * express it. 090 * 091 * <p> 092 * The context calls this only for built-in leaves; it handles the {@code $and}/{@code $or}/{@code $not} combinators 093 * itself. A <jk>null</jk> return makes the context fail with a clear error rather than emit wrong SQL. 094 * 095 * @param operatorName The built-in operator name, including the leading {@code $}. Must not be <jk>null</jk>. 096 * @return The renderer, or <jk>null</jk> if unsupported. 097 */ 098 SearchSqlRenderer builtinRenderer(String operatorName); 099 100 /** 101 * Whether this dialect orders {@link SearchType#VERSION} columns as dotted integer tuples, like the in-memory engine 102 * (so {@code 9.10} is greater than {@code 9.9}). 103 * 104 * <p> 105 * When <jk>false</jk> (the default), a range or ordering operator ({@code $gt}, {@code $gte}, {@code $lt}, 106 * {@code $lte}, {@code $between}) on a {@code VERSION} column is rejected with 107 * {@link org.apache.juneau.commons.beanquery.BeanQuerySyntaxException.Code#BAD_VALUE BAD_VALUE} instead of silently comparing text (where {@code 9.10} 108 * sorts before {@code 9.9}). Equality, {@code $in}, {@code $ne} and {@code $prefix} are not affected. 109 * 110 * <h5 class='section'>Example:</h5> 111 * <p class='bjava'> 112 * <jc>// A dialect that compares versions as integer tuples opts in.</jc> 113 * <ja>@Override</ja> <jk>public boolean</jk> supportsVersionOrdering() { <jk>return true</jk>; } 114 * </p> 115 * 116 * @return <jk>true</jk> if version range comparisons match the in-memory semantics. 117 */ 118 default boolean supportsVersionOrdering() { 119 return false; 120 } 121 122 /** 123 * Renders the pagination clause appended to a {@code SELECT} (for example {@code " LIMIT 10 OFFSET 20"}). 124 * 125 * <p> 126 * The limit/offset are inlined as integer literals (never bind values), so the returned text carries no 127 * placeholders. 128 * 129 * @param limit The maximum row count, or <jk>null</jk> for no limit. 130 * @param offset The zero-based start offset, or <jk>null</jk>/{@code 0} for none. 131 * @return The pagination clause (possibly empty, never <jk>null</jk>). 132 */ 133 String renderLimitOffset(Long limit, Long offset); 134}