[#6012] Implement specialised Name for unqualified Names

This commit is contained in:
lukaseder 2017-03-24 22:25:14 +01:00
parent ec403fa8c0
commit c938846377
5 changed files with 336 additions and 138 deletions

View File

@ -49,147 +49,24 @@ import org.jooq.Record;
import org.jooq.Select;
import org.jooq.WindowDefinition;
import org.jooq.WindowSpecification;
import org.jooq.tools.StringUtils;
/**
* The default implementation for a SQL identifier
* The default implementation for a qualified SQL identifier.
*
* @author Lukas Eder
*/
final class NameImpl extends AbstractQueryPart implements Name {
abstract class AbstractName extends AbstractQueryPart implements Name {
/**
* Generated UID
*/
private static final long serialVersionUID = 8562325639223483938L;
private final String[] qualifiedName;
private final Boolean[] quoted;
NameImpl(String[] qualifiedName) {
this(qualifiedName, new Boolean[qualifiedName.length]);
}
NameImpl(Name[] qualifiedName) {
this(last(qualifiedName), quoted(qualifiedName));
}
NameImpl(String[] qualifiedName, boolean quoted) {
this(qualifiedName, booleans(quoted, qualifiedName.length));
}
NameImpl(String[] qualifiedName, Boolean[] quoted) {
this.qualifiedName = nonEmpty(qualifiedName);
this.quoted = quoted;
}
private static final Boolean[] booleans(boolean val, int length) {
Boolean[] result = new Boolean[length];
Arrays.fill(result, val);
return result;
}
private static final String[] last(Name[] qualifiedName) {
String[] result = new String[qualifiedName.length];
for (int i = 0; i < qualifiedName.length; i++)
result[i] = qualifiedName[i].last();
return result;
}
private static final Boolean[] quoted(Name[] qualifiedName) {
Boolean[] result = new Boolean[qualifiedName.length];
for (int i = 0; i < qualifiedName.length; i++)
result[i] = ((NameImpl) qualifiedName[i]).quoted[((NameImpl) qualifiedName[i]).quoted.length - 1];
return result;
}
private static final String[] nonEmpty(String[] qualifiedName) {
String[] result;
int nulls = 0;
for (int i = 0; i < qualifiedName.length; i++)
if (StringUtils.isEmpty(qualifiedName[i]))
nulls++;
if (nulls > 0) {
result = new String[qualifiedName.length - nulls];
for (int i = qualifiedName.length - 1; i >= 0; i--)
if (StringUtils.isEmpty(qualifiedName[i]))
nulls--;
else
result[i - nulls] = qualifiedName[i];
}
else {
result = qualifiedName.clone();
}
return result;
}
@Override
public final void accept(Context<?> ctx) {
boolean previous = ctx.quote();
// [#3437] Fully qualify this field only if allowed in the current context
if (ctx.qualify()) {
String separator = "";
for (int i = 0; i < qualifiedName.length; i++) {
String name = qualifiedName[i];
if (quoted[i] != null)
ctx.quote(quoted[i]);
ctx.sql(separator).literal(name);
if (quoted[i] != null)
ctx.quote(previous);
separator = ".";
}
}
else {
int last = quoted.length - 1;
if (quoted[last] != null)
ctx.quote(quoted[last]);
ctx.literal(last());
if (quoted[last] != null)
ctx.quote(previous);
}
}
@Override
public final Clause[] clauses(Context<?> ctx) {
return null;
}
@Override
public final String first() {
return qualifiedName.length > 0 ? qualifiedName[0] : null;
}
@Override
public final String last() {
return qualifiedName.length > 0 ? qualifiedName[qualifiedName.length - 1] : null;
}
@Override
public final boolean qualified() {
return qualifiedName.length > 1;
}
@Override
public final String[] getName() {
return qualifiedName;
}
@Override
public final WindowDefinition as(WindowSpecification window) {
return new WindowDefinitionImpl(this, window);
@ -203,10 +80,10 @@ final class NameImpl extends AbstractQueryPart implements Name {
@Override
public final DerivedColumnListImpl fields(String... fieldNames) {
if (qualifiedName.length != 1)
throw new IllegalStateException("Cannot create a DerivedColumnList from a qualified name : " + Arrays.asList(qualifiedName));
if (getName().length != 1)
throw new IllegalStateException("Cannot create a DerivedColumnList from a qualified name : " + Arrays.asList(getName()));
return new DerivedColumnListImpl(first(), fieldNames);
return new DerivedColumnListImpl(last(), fieldNames);
}
@ -375,8 +252,8 @@ final class NameImpl extends AbstractQueryPart implements Name {
// [#1626] NameImpl equality can be decided without executing the
// rather expensive implementation of AbstractQueryPart.equals()
if (that instanceof NameImpl)
return Arrays.equals(getName(), (((NameImpl) that).getName()));
if (that instanceof AbstractName)
return Arrays.equals(getName(), (((AbstractName) that).getName()));
return super.equals(that);
}

View File

@ -7227,6 +7227,33 @@ public class DSL {
// XXX Names
// -------------------------------------------------------------------------
/**
* Create a new SQL identifier using an unqualified name.
* <p>
* Use this method to construct syntax-safe, SQL-injection-safe SQL
* identifiers for use in plain SQL where {@link QueryPart} objects are
* accepted. For instance, this can be used with any of these methods:
* <ul>
* <li> {@link #field(Name)}</li>
* <li> {@link #field(Name, Class)}</li>
* <li> {@link #field(Name, DataType)}</li>
* </ul>
* <p>
* An example: <code><pre>
* // This unqualified name here
* name("book");
*
* // ... will render this SQL on SQL Server with RenderNameStyle.QUOTED set
* [book].[title]
* </pre></code>
*
* @param unqualifiedName The SQL identifier's unqualified name
* @return A {@link QueryPart} that will render the SQL identifier
*/
public static Name name(String unqualifiedName) {
return new UnqualifiedName(unqualifiedName);
}
/**
* Create a new SQL identifier using a qualified name.
* <p>
@ -7251,7 +7278,10 @@ public class DSL {
* @return A {@link QueryPart} that will render the SQL identifier
*/
public static Name name(String... qualifiedName) {
return new NameImpl(qualifiedName);
if (qualifiedName == null || qualifiedName.length != 1)
return new QualifiedName(qualifiedName);
else
return new UnqualifiedName(qualifiedName[0]);
}
/**
@ -7282,7 +7312,7 @@ public class DSL {
* @return A {@link QueryPart} that will render the SQL identifier
*/
public static Name name(Name... nameParts) {
return new NameImpl(nameParts);
return new QualifiedName(nameParts);
}
/**
@ -7309,7 +7339,21 @@ public class DSL {
* @return A {@link QueryPart} that will render the SQL identifier
*/
public static Name name(Collection<String> qualifiedName) {
return new NameImpl(qualifiedName.toArray(Tools.EMPTY_STRING));
return name(qualifiedName.toArray(Tools.EMPTY_STRING));
}
/**
* Create a new SQL identifier using an unqualified, quoted name.
* <p>
* This works like {@link #name(String...)}, except that generated
* identifiers will be guaranteed to be quoted in databases that support
* quoted identifiers.
*
* @param unqualifiedName The SQL identifier's unqualified name
* @return A {@link QueryPart} that will render the SQL identifier
*/
public static Name quotedName(String unqualifiedName) {
return new UnqualifiedName(unqualifiedName, true);
}
/**
@ -7323,7 +7367,7 @@ public class DSL {
* @return A {@link QueryPart} that will render the SQL identifier
*/
public static Name quotedName(String... qualifiedName) {
return new NameImpl(qualifiedName, true);
return new QualifiedName(qualifiedName, true);
}
/**
@ -7337,7 +7381,21 @@ public class DSL {
* @return A {@link QueryPart} that will render the SQL identifier
*/
public static Name quotedName(Collection<String> qualifiedName) {
return new NameImpl(qualifiedName.toArray(Tools.EMPTY_STRING), true);
return quotedName(qualifiedName.toArray(Tools.EMPTY_STRING));
}
/**
* Create a new SQL identifier using an unqualified, quoted name.
* <p>
* This works like {@link #name(String...)}, except that generated
* identifiers will be guaranteed to be quoted in databases that support
* quoted identifiers.
*
* @param unqualifiedName The SQL identifier's unqualified name
* @return A {@link QueryPart} that will render the SQL identifier
*/
public static Name unquotedName(String unqualifiedName) {
return new UnqualifiedName(unqualifiedName, false);
}
/**
@ -7351,7 +7409,10 @@ public class DSL {
* @return A {@link QueryPart} that will render the SQL identifier
*/
public static Name unquotedName(String... qualifiedName) {
return new NameImpl(qualifiedName, false);
if (qualifiedName == null || qualifiedName.length != 1)
return new QualifiedName(qualifiedName, false);
else
return new UnqualifiedName(qualifiedName[0], false);
}
/**
@ -7365,7 +7426,7 @@ public class DSL {
* @return A {@link QueryPart} that will render the SQL identifier
*/
public static Name unquotedName(Collection<String> qualifiedName) {
return new NameImpl(qualifiedName.toArray(Tools.EMPTY_STRING), false);
return unquotedName(qualifiedName.toArray(Tools.EMPTY_STRING));
}
// -------------------------------------------------------------------------

View File

@ -67,7 +67,7 @@ final class QualifiedField<T> extends AbstractField<T> implements TableField<Rec
super(DSL.name(defaultIfNull(name.last(), "")), type);
this.name = name;
this.table = name.getName().length > 1
this.table = name.qualified()
? DSL.table(DSL.name(Arrays.copyOf(name.getName(), name.getName().length - 1)))
: null;
}

View File

@ -0,0 +1,165 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.impl;
import org.jooq.Context;
import org.jooq.Name;
import org.jooq.tools.StringUtils;
/**
* The default implementation for a qualified SQL identifier.
*
* @author Lukas Eder
*/
final class QualifiedName extends AbstractName {
/**
* Generated UID
*/
private static final long serialVersionUID = 8562325639223483938L;
private final UnqualifiedName[] qualifiedName;
QualifiedName(String[] qualifiedName) {
this(qualifiedName, null);
}
QualifiedName(String[] qualifiedName, Boolean quoted) {
this(names(qualifiedName, quoted));
}
QualifiedName(Name[] qualifiedName) {
this.qualifiedName = last(qualifiedName);
}
private QualifiedName(UnqualifiedName[] qualifiedName) {
this.qualifiedName = qualifiedName;
}
private static final UnqualifiedName[] names(String[] qualifiedName, Boolean quoted) {
String[] nonEmpty = nonEmpty(qualifiedName);
UnqualifiedName[] result = new UnqualifiedName[nonEmpty.length];
for (int i = 0; i < nonEmpty.length; i++)
result[i] = new UnqualifiedName(nonEmpty[i], quoted);
return result;
}
private static final UnqualifiedName[] last(Name[] qualifiedName) {
if (qualifiedName instanceof UnqualifiedName[])
return (UnqualifiedName[]) qualifiedName;
UnqualifiedName[] result = new UnqualifiedName[qualifiedName.length];
for (int i = 0; i < qualifiedName.length; i++)
if (qualifiedName[i] instanceof QualifiedName) {
QualifiedName q = (QualifiedName) qualifiedName[i];
result[i] = q.qualifiedName[q.qualifiedName.length - 1];
}
else if (qualifiedName[i] instanceof UnqualifiedName)
result[i] = (UnqualifiedName) qualifiedName[i];
else
result[i] = new UnqualifiedName(qualifiedName[i].last());
return result;
}
private static final String[] nonEmpty(String[] qualifiedName) {
String[] result;
int nulls = 0;
for (int i = 0; i < qualifiedName.length; i++)
if (StringUtils.isEmpty(qualifiedName[i]))
nulls++;
if (nulls > 0) {
result = new String[qualifiedName.length - nulls];
for (int i = qualifiedName.length - 1; i >= 0; i--)
if (StringUtils.isEmpty(qualifiedName[i]))
nulls--;
else
result[i - nulls] = qualifiedName[i];
}
else {
result = qualifiedName.clone();
}
return result;
}
@Override
public final void accept(Context<?> ctx) {
// [#3437] Fully qualify this field only if allowed in the current context
if (ctx.qualify()) {
String separator = "";
for (int i = 0; i < qualifiedName.length; i++) {
ctx.sql(separator).visit(qualifiedName[i]);
separator = ".";
}
}
else {
ctx.visit(qualifiedName[qualifiedName.length - 1]);
}
}
@Override
public final String first() {
return qualifiedName.length > 0 ? qualifiedName[0].last() : null;
}
@Override
public final String last() {
return qualifiedName.length > 0 ? qualifiedName[qualifiedName.length - 1].last() : null;
}
@Override
public final boolean qualified() {
return qualifiedName.length > 1;
}
@Override
public final String[] getName() {
String[] result = new String[qualifiedName.length];
for (int i = 0; i < qualifiedName.length; i++)
result[i] = qualifiedName[i].last();
return result;
}
}

View File

@ -0,0 +1,95 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.impl;
import org.jooq.Context;
/**
* The default implementation for an unqualified SQL identifier.
*
* @author Lukas Eder
*/
final class UnqualifiedName extends AbstractName {
/**
* Generated UID
*/
private static final long serialVersionUID = 8562325639223483938L;
private final String name;
private final Boolean quoted;
UnqualifiedName(String name) {
this(name, null);
}
UnqualifiedName(String name, Boolean quoted) {
this.name = name;
this.quoted = quoted;
}
@Override
public final void accept(Context<?> ctx) {
boolean previous = ctx.quote();
if (quoted != null)
ctx.quote(quoted);
ctx.literal(name);
if (quoted != null)
ctx.quote(previous);
}
@Override
public final String first() {
return name;
}
@Override
public final String last() {
return name;
}
@Override
public final boolean qualified() {
return false;
}
@Override
public final String[] getName() {
return new String[] { name };
}
}