diff --git a/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/AbstractMetaObjectCommand.java b/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/AbstractMetaObjectCommand.java index 1ae04ce..5253cad 100644 --- a/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/AbstractMetaObjectCommand.java +++ b/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/AbstractMetaObjectCommand.java @@ -31,6 +31,7 @@ import picocli.CommandLine; import java.util.Collection; +import java.util.List; @CommandLine.Command public abstract class AbstractMetaObjectCommand extends CLIAbstractSubcommand { @@ -129,6 +130,28 @@ private boolean containsObjectOfType(// in case if schema/database not specified return false; } + @Nullable + public DBSObjectContainer resolveContainerByPath( + @NotNull DBRProgressMonitor monitor, + @NotNull DBPDataSource dataSource, + @NotNull List pathTokens + ) throws DBException { + if (!(dataSource instanceof DBSObjectContainer container)) { + if (pathTokens.isEmpty()) { + return null; + } + throw new CLIException( + "Datasource '" + dataSource.getContainer().getName() + "' does not support nested objects", + CLIConstants.EXIT_CODE_ERROR + ); + } + DBSObjectContainer current = container; + for (String token : pathTokens) { + current = getChildContainer(monitor, current, token); + } + return current; + } + @NotNull protected DBSObjectContainer getChildContainer( @NotNull DBRProgressMonitor monitor, diff --git a/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/MetaFullNameOptions.java b/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/MetaFullNameOptions.java new file mode 100644 index 0000000..7ac54d5 --- /dev/null +++ b/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/MetaFullNameOptions.java @@ -0,0 +1,129 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2026 DBeaver Corp and others + * + * 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. + */ +package org.dbvr.cli.sql.meta; + +import org.jkiss.code.NotNull; +import org.jkiss.code.Nullable; +import org.jkiss.dbeaver.model.DBPDataSource; +import org.jkiss.dbeaver.model.cli.CLIConstants; +import org.jkiss.dbeaver.model.cli.CLIException; +import org.jkiss.dbeaver.model.sql.SQLUtils; +import org.jkiss.utils.CommonUtils; +import picocli.CommandLine; + +import java.util.Arrays; +import java.util.List; + +public class MetaFullNameOptions { + + @Nullable + @CommandLine.Option( + names = {"--full-name", "-fn"}, + description = "Fully qualified name (e.g. database.schema.table)" + ) + protected String fullName; + + public record Resolved( + boolean fromFullName, + @NotNull List containerPath, + @Nullable String databaseName, + @Nullable String schemaName, + @Nullable String objectName + ) { + @NotNull + public static Resolved fromExplicit( + @Nullable String databaseName, + @Nullable String schemaName, + @Nullable String objectName + ) { + return new Resolved(false, List.of(), databaseName, schemaName, objectName); + } + + @NotNull + public static Resolved fromTokens(@NotNull String[] tokens, boolean hasObject) { + if (!hasObject) { + return new Resolved(true, List.of(tokens), null, null, null); + } + // (table ddl -fn=db.schema.table → path=[db, schema], object=table). + String objectName = tokens[tokens.length - 1]; + List path = List.of(Arrays.copyOfRange(tokens, 0, tokens.length - 1)); + return new Resolved(true, path, null, null, objectName); + } + } + + public boolean isSpecified() { + return CommonUtils.isNotEmpty(fullName); + } + + @NotNull + public Resolved resolve( + @NotNull DBPDataSource dataSource, + @Nullable String explicitDatabase, + @Nullable String explicitSchema, + @Nullable String explicitObject, + int maxContainerDepth, + boolean hasObject + ) throws CLIException { + if (!isSpecified()) { + return Resolved.fromExplicit(explicitDatabase, explicitSchema, explicitObject); + } + rejectIfAnyExplicit(explicitDatabase, explicitSchema, explicitObject); + String[][] quoteStrings = dataSource.getSQLDialect().getIdentifierQuoteStrings(); + return Resolved.fromTokens(splitAndValidate(maxContainerDepth, hasObject, quoteStrings), hasObject); + } + + private static void rejectIfAnyExplicit( + @Nullable String database, + @Nullable String schema, + @Nullable String object + ) throws CLIException { + if (CommonUtils.isNotEmpty(database) || CommonUtils.isNotEmpty(schema) || CommonUtils.isNotEmpty(object)) { + throw new CLIException( + "--full-name cannot be combined with --database-name, --schema-name or the object name option", + CLIConstants.EXIT_CODE_ILLEGAL_ARGUMENTS + ); + } + } + + @NotNull + private String[] splitAndValidate( + int maxContainerDepth, + boolean hasObject, + @Nullable String[][] quoteStrings + ) throws CLIException { + String trimmed = fullName == null ? "" : fullName.trim(); + if (trimmed.isEmpty()) { + throw new CLIException("Fully qualified name is empty", CLIConstants.EXIT_CODE_ILLEGAL_ARGUMENTS); + } + String[] tokens = SQLUtils.splitFullIdentifier(trimmed, ".", quoteStrings, false); + for (String token : tokens) { + if (token.isEmpty()) { + throw new CLIException("Invalid fully qualified name: '" + fullName + "'", + CLIConstants.EXIT_CODE_ILLEGAL_ARGUMENTS); + } + } + int maxParts = maxContainerDepth + (hasObject ? 1 : 0); + if (tokens.length > maxParts) { + throw new CLIException( + "Fully qualified name '" + fullName + "' has " + tokens.length + + " parts but at most " + maxParts + " is allowed for this command", + CLIConstants.EXIT_CODE_ILLEGAL_ARGUMENTS + ); + } + return tokens; + } +} diff --git a/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/ddl/AbstractDDLCommand.java b/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/ddl/AbstractDDLCommand.java index 96a347c..f134980 100644 --- a/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/ddl/AbstractDDLCommand.java +++ b/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/ddl/AbstractDDLCommand.java @@ -49,7 +49,7 @@ public abstract class AbstractDDLCommand extends AbstractMetaCommand { protected abstract String getObjectTypeName(); @Nullable - protected abstract String getTargetObjectName(); + protected abstract String getTargetObjectName(@NotNull DBPDataSource dataSource) throws DBException; @Nullable protected abstract DBSObjectContainer getBaseContainer( @@ -66,7 +66,7 @@ protected void execute(@NotNull DBRProgressMonitor monitor) throws DBException { AbstractMetaObjectCommand parent = getParentCommand(); DBPDataSource dataSource = connectDataSource(); checkDDLSupported(monitor, dataSource); - String objectName = getTargetObjectName(); + String objectName = getTargetObjectName(dataSource); if (CommonUtils.isEmpty(objectName)) { throw new CLIException( getObjectTypeName() + " name is not specified", diff --git a/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/ddl/DatabaseDDLCommand.java b/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/ddl/DatabaseDDLCommand.java index d22f1b2..7d32b3c 100644 --- a/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/ddl/DatabaseDDLCommand.java +++ b/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/ddl/DatabaseDDLCommand.java @@ -47,6 +47,9 @@ public class DatabaseDDLCommand extends AbstractDDLCommand { @CommandLine.Mixin private MetaDatabaseOptions containerOptions; + @CommandLine.Mixin + private MetaFullNameOptions fullNameOptions; + @NotNull @Override protected AbstractMetaObjectCommand getParentCommand() { @@ -61,8 +64,15 @@ protected String getObjectTypeName() { @Nullable @Override - protected String getTargetObjectName() { - return containerOptions.getDatabaseName(); + protected String getTargetObjectName(@NotNull DBPDataSource dataSource) throws DBException { + return fullNameOptions.resolve( + dataSource, + null, + null, + containerOptions.getDatabaseName(), + 0, + true + ).objectName(); } @Nullable diff --git a/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/ddl/SchemaDDLCommand.java b/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/ddl/SchemaDDLCommand.java index fe96cd7..492d37d 100644 --- a/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/ddl/SchemaDDLCommand.java +++ b/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/ddl/SchemaDDLCommand.java @@ -17,6 +17,7 @@ package org.dbvr.cli.sql.meta.ddl; import org.dbvr.cli.sql.meta.AbstractMetaObjectCommand; +import org.dbvr.cli.sql.meta.MetaFullNameOptions; import org.dbvr.cli.sql.meta.MetaSchemaOptions; import org.dbvr.cli.sql.meta.SchemaCommand; import org.jkiss.code.NotNull; @@ -36,6 +37,26 @@ public class SchemaDDLCommand extends AbstractDDLCommand { @CommandLine.Mixin private MetaSchemaOptions containerOptions; + @CommandLine.Mixin + private MetaFullNameOptions fullNameOptions; + + private MetaFullNameOptions.Resolved resolved; + + @NotNull + private MetaFullNameOptions.Resolved resolved(@NotNull DBPDataSource dataSource) throws DBException { + if (resolved == null) { + resolved = fullNameOptions.resolve( + dataSource, + containerOptions.getDatabaseName(), + null, + containerOptions.getSchemaName(), + 1, + true + ); + } + return resolved; + } + @NotNull @Override protected AbstractMetaObjectCommand getParentCommand() { @@ -50,8 +71,8 @@ protected String getObjectTypeName() { @Nullable @Override - protected String getTargetObjectName() { - return containerOptions.getSchemaName(); + protected String getTargetObjectName(@NotNull DBPDataSource dataSource) throws DBException { + return resolved(dataSource).objectName(); } @Nullable @@ -60,6 +81,10 @@ protected DBSObjectContainer getBaseContainer( @NotNull DBRProgressMonitor monitor, @NotNull DBPDataSource dataSource ) throws DBException { - return parent.getBaseContainer(monitor, dataSource, containerOptions.getDatabaseName(), null); + MetaFullNameOptions.Resolved r = resolved(dataSource); + if (r.fromFullName()) { + return parent.resolveContainerByPath(monitor, dataSource, r.containerPath()); + } + return parent.getBaseContainer(monitor, dataSource, r.databaseName(), null); } } diff --git a/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/ddl/TableDDLCommand.java b/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/ddl/TableDDLCommand.java index 7f444a7..2237064 100644 --- a/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/ddl/TableDDLCommand.java +++ b/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/ddl/TableDDLCommand.java @@ -17,6 +17,7 @@ package org.dbvr.cli.sql.meta.ddl; import org.dbvr.cli.sql.meta.AbstractMetaObjectCommand; +import org.dbvr.cli.sql.meta.MetaFullNameOptions; import org.dbvr.cli.sql.meta.MetaSchemaOptions; import org.dbvr.cli.sql.meta.TableCommand; import org.jkiss.code.NotNull; @@ -36,10 +37,30 @@ public class TableDDLCommand extends AbstractDDLCommand { @CommandLine.Mixin private MetaSchemaOptions containerOptions; + @CommandLine.Mixin + private MetaFullNameOptions fullNameOptions; + @Nullable @CommandLine.Option(names = {"--table-name", "-tn"}, description = "Table name") protected String tableName; + private MetaFullNameOptions.Resolved resolved; + + @NotNull + private MetaFullNameOptions.Resolved resolved(@NotNull DBPDataSource dataSource) throws DBException { + if (resolved == null) { + resolved = fullNameOptions.resolve( + dataSource, + containerOptions.getDatabaseName(), + containerOptions.getSchemaName(), + tableName, + 2, + true + ); + } + return resolved; + } + @NotNull @Override protected AbstractMetaObjectCommand getParentCommand() { @@ -54,8 +75,8 @@ protected String getObjectTypeName() { @Nullable @Override - protected String getTargetObjectName() { - return tableName; + protected String getTargetObjectName(@NotNull DBPDataSource dataSource) throws DBException { + return resolved(dataSource).objectName(); } @Nullable @@ -64,6 +85,10 @@ protected DBSObjectContainer getBaseContainer( @NotNull DBRProgressMonitor monitor, @NotNull DBPDataSource dataSource ) throws DBException { - return parent.getBaseContainer(monitor, dataSource, containerOptions.getDatabaseName(), containerOptions.getSchemaName()); + MetaFullNameOptions.Resolved r = resolved(dataSource); + if (r.fromFullName()) { + return parent.resolveContainerByPath(monitor, dataSource, r.containerPath()); + } + return parent.getBaseContainer(monitor, dataSource, r.databaseName(), r.schemaName()); } } diff --git a/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/list/SchemaListCommand.java b/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/list/SchemaListCommand.java index 19dcd99..29831d0 100644 --- a/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/list/SchemaListCommand.java +++ b/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/list/SchemaListCommand.java @@ -17,6 +17,7 @@ package org.dbvr.cli.sql.meta.list; import org.dbvr.cli.sql.meta.AbstractMetaObjectCommand; +import org.dbvr.cli.sql.meta.MetaFullNameOptions; import org.dbvr.cli.sql.meta.MetaSchemaOptions; import org.dbvr.cli.sql.meta.SchemaCommand; import org.jkiss.code.NotNull; @@ -36,6 +37,9 @@ public class SchemaListCommand extends AbstractListCommand { @CommandLine.Mixin private MetaSchemaOptions containerOptions; + @CommandLine.Mixin + private MetaFullNameOptions fullNameOptions; + @NotNull @Override protected AbstractMetaObjectCommand getParentCommand() { @@ -54,6 +58,17 @@ protected DBSObjectContainer getBaseContainer( @NotNull DBRProgressMonitor monitor, @NotNull DBPDataSource dataSource ) throws DBException { - return parent.getBaseContainer(monitor, dataSource, containerOptions.getDatabaseName(), null); + MetaFullNameOptions.Resolved r = fullNameOptions.resolve( + dataSource, + containerOptions.getDatabaseName(), + null, + null, + 1, + false + ); + if (r.fromFullName()) { + return parent.resolveContainerByPath(monitor, dataSource, r.containerPath()); + } + return parent.getBaseContainer(monitor, dataSource, r.databaseName(), null); } } diff --git a/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/list/TableListCommand.java b/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/list/TableListCommand.java index 313adeb..ef4252e 100644 --- a/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/list/TableListCommand.java +++ b/bundles/org.dbvr.cli.sql/src/org/dbvr/cli/sql/meta/list/TableListCommand.java @@ -17,6 +17,7 @@ package org.dbvr.cli.sql.meta.list; import org.dbvr.cli.sql.meta.AbstractMetaObjectCommand; +import org.dbvr.cli.sql.meta.MetaFullNameOptions; import org.dbvr.cli.sql.meta.MetaSchemaOptions; import org.dbvr.cli.sql.meta.TableCommand; import org.jkiss.code.NotNull; @@ -36,6 +37,9 @@ public class TableListCommand extends AbstractListCommand { @CommandLine.Mixin private MetaSchemaOptions containerOptions; + @CommandLine.Mixin + private MetaFullNameOptions fullNameOptions; + @NotNull @Override protected AbstractMetaObjectCommand getParentCommand() { @@ -54,6 +58,17 @@ protected DBSObjectContainer getBaseContainer( @NotNull DBRProgressMonitor monitor, @NotNull DBPDataSource dataSource ) throws DBException { - return parent.getBaseContainer(monitor, dataSource, containerOptions.getDatabaseName(), containerOptions.getSchemaName()); + MetaFullNameOptions.Resolved r = fullNameOptions.resolve( + dataSource, + containerOptions.getDatabaseName(), + containerOptions.getSchemaName(), + null, + 2, + false + ); + if (r.fromFullName()) { + return parent.resolveContainerByPath(monitor, dataSource, r.containerPath()); + } + return parent.getBaseContainer(monitor, dataSource, r.databaseName(), r.schemaName()); } }