diff --git a/jOOQ/src/main/java/org/jooq/impl/NameImpl.java b/jOOQ/src/main/java/org/jooq/impl/AbstractName.java similarity index 80% rename from jOOQ/src/main/java/org/jooq/impl/NameImpl.java rename to jOOQ/src/main/java/org/jooq/impl/AbstractName.java index b66dda0ea3..4805af3d3a 100644 --- a/jOOQ/src/main/java/org/jooq/impl/NameImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractName.java @@ -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); } diff --git a/jOOQ/src/main/java/org/jooq/impl/DSL.java b/jOOQ/src/main/java/org/jooq/impl/DSL.java index aa25c7a8d5..d35109fc1d 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DSL.java +++ b/jOOQ/src/main/java/org/jooq/impl/DSL.java @@ -7227,6 +7227,33 @@ public class DSL { // XXX Names // ------------------------------------------------------------------------- + /** + * Create a new SQL identifier using an unqualified name. + *

+ * 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: + *

+ *

+ * An example:

+     * // This unqualified name here
+     * name("book");
+     *
+     * // ... will render this SQL on SQL Server with RenderNameStyle.QUOTED set
+     * [book].[title]
+     * 
+ * + * @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. *

@@ -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 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. + *

+ * 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 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. + *

+ * 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 qualifiedName) { - return new NameImpl(qualifiedName.toArray(Tools.EMPTY_STRING), false); + return unquotedName(qualifiedName.toArray(Tools.EMPTY_STRING)); } // ------------------------------------------------------------------------- diff --git a/jOOQ/src/main/java/org/jooq/impl/QualifiedField.java b/jOOQ/src/main/java/org/jooq/impl/QualifiedField.java index 7cb782dd7a..3d3bc2d9d4 100644 --- a/jOOQ/src/main/java/org/jooq/impl/QualifiedField.java +++ b/jOOQ/src/main/java/org/jooq/impl/QualifiedField.java @@ -67,7 +67,7 @@ final class QualifiedField extends AbstractField implements TableField 1 + this.table = name.qualified() ? DSL.table(DSL.name(Arrays.copyOf(name.getName(), name.getName().length - 1))) : null; } diff --git a/jOOQ/src/main/java/org/jooq/impl/QualifiedName.java b/jOOQ/src/main/java/org/jooq/impl/QualifiedName.java new file mode 100644 index 0000000000..b3c9d38e55 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/QualifiedName.java @@ -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; + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/UnqualifiedName.java b/jOOQ/src/main/java/org/jooq/impl/UnqualifiedName.java new file mode 100644 index 0000000000..9c94f24808 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/UnqualifiedName.java @@ -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 }; + } +}