diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/JDBCEnvironment.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/JDBCEnvironment.java index 19544447d..18f5b0517 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/JDBCEnvironment.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/JDBCEnvironment.java @@ -67,6 +67,19 @@ public abstract class JDBCEnvironment extends DelegatingEnvironment { */ public abstract int getAbsoluteMaxConnections() throws GuacamoleException; + /** + * Returns the maximum number of identifiers/parameters to be + * included in a single batch when executing SQL statements. + * + * @return + * The maximum number of identifiers/parameters to be included + * in a single batch. + * + * @throws GuacamoleException + * If an error occurs while retrieving the property. + */ + public abstract int getBatchSize() throws GuacamoleException; + /** * Returns the default maximum number of concurrent connections to allow to * any one connection, unless specified differently on an individual diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ModeledDirectoryObjectService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ModeledDirectoryObjectService.java index 80bc7eb5c..fe587160d 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ModeledDirectoryObjectService.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/base/ModeledDirectoryObjectService.java @@ -19,13 +19,18 @@ package org.apache.guacamole.auth.jdbc.base; +import com.google.common.collect.Lists; +import com.google.inject.Inject; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import org.apache.guacamole.auth.jdbc.user.ModeledAuthenticatedUser; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleSecurityException; +import org.apache.guacamole.auth.jdbc.JDBCEnvironment; import org.apache.guacamole.auth.jdbc.permission.ObjectPermissionMapper; import org.apache.guacamole.auth.jdbc.permission.ObjectPermissionModel; import org.apache.guacamole.auth.jdbc.user.UserModel; @@ -66,6 +71,12 @@ public abstract class ModeledDirectoryObjectService filterIdentifiers(Collection identifiers) { + protected List filterIdentifiers(Collection identifiers) { // Obtain enough space for a full copy of the given identifiers - Collection validIdentifiers = new ArrayList(identifiers.size()); + List validIdentifiers = new ArrayList<>(identifiers.size()); // Add only valid identifiers to the copy for (String identifier : identifiers) { @@ -387,26 +398,36 @@ public abstract class ModeledDirectoryObjectService identifiers) throws GuacamoleException { // Ignore invalid identifiers - identifiers = filterIdentifiers(identifiers); + List filteredIdentifiers = filterIdentifiers(identifiers); // Do not query if no identifiers given - if (identifiers.isEmpty()) + if (filteredIdentifiers.isEmpty()) return Collections.emptyList(); - Collection objects; + int batchSize = environment.getBatchSize(); - // Bypass permission checks if the user is privileged - if (user.isPrivileged()) - objects = getObjectMapper().select(identifiers); + boolean userIsPrivileged = user.isPrivileged(); + + // Process the filteredIdentifiers in batches using Lists.partition() and flatMap + Collection allObjects = Lists.partition(filteredIdentifiers, batchSize).stream() + .flatMap(chunk -> { + Collection objects; + + // Bypass permission checks if the user is privileged + if (userIsPrivileged) + objects = getObjectMapper().select(chunk); + + // Otherwise only return explicitly readable identifiers + else + objects = getObjectMapper().selectReadable(user.getUser().getModel(), + chunk, user.getEffectiveUserGroups()); + + return objects.stream(); + }) + .collect(Collectors.toList()); - // Otherwise only return explicitly readable identifiers - else - objects = getObjectMapper().selectReadable(user.getUser().getModel(), - identifiers, user.getEffectiveUserGroups()); - // Return collection of requested objects - return getObjectInstances(user, objects); - + return getObjectInstances(user, allObjects); } /** diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/conf/MySQLEnvironment.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/conf/MySQLEnvironment.java index ee3fd8aa1..a18e634c0 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/conf/MySQLEnvironment.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/conf/MySQLEnvironment.java @@ -104,6 +104,20 @@ public class MySQLEnvironment extends JDBCEnvironment { * The default SSL mode for connecting to MySQL servers. */ private final MySQLSSLMode DEFAULT_SSL_MODE = MySQLSSLMode.PREFERRED; + + /** + * The default maximum number of identifiers/parameters to be included in a + * single batch when executing SQL statements for MySQL and MariaDB. + * + * MySQL and MariaDB impose a limit on the maximum size of a query, + * determined by the max_allowed_packet configuration variable. A value of + * 1000 is chosen to accommodate the max_allowed_packet limit without + * exceeding it. + * + * @see https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_max_allowed_packet + * @see https://mariadb.com/kb/en/server-system-variables/#max_allowed_packet + */ + private static final int DEFAULT_BATCH_SIZE = 1000; /** * Constructs a new MySQLEnvironment, providing access to MySQL-specific @@ -134,6 +148,13 @@ public class MySQLEnvironment extends JDBCEnvironment { DEFAULT_ABSOLUTE_MAX_CONNECTIONS ); } + + @Override + public int getBatchSize() throws GuacamoleException { + return getProperty(MySQLGuacamoleProperties.MYSQL_BATCH_SIZE, + DEFAULT_BATCH_SIZE + ); + } @Override public int getDefaultMaxConnections() throws GuacamoleException { diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/conf/MySQLGuacamoleProperties.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/conf/MySQLGuacamoleProperties.java index 925f82a72..85d93778e 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/conf/MySQLGuacamoleProperties.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/conf/MySQLGuacamoleProperties.java @@ -265,4 +265,16 @@ public class MySQLGuacamoleProperties { }; + /** + * The maximum number of identifiers/parameters to be included in a single batch when + * executing SQL statements. + */ + public static final IntegerGuacamoleProperty MYSQL_BATCH_SIZE = + new IntegerGuacamoleProperty() { + + @Override + public String getName() { return "mysql-batch-size"; } + + }; + } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/java/org/apache/guacamole/auth/postgresql/conf/PostgreSQLEnvironment.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/java/org/apache/guacamole/auth/postgresql/conf/PostgreSQLEnvironment.java index 2ead15b6e..fc615cdcf 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/java/org/apache/guacamole/auth/postgresql/conf/PostgreSQLEnvironment.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/java/org/apache/guacamole/auth/postgresql/conf/PostgreSQLEnvironment.java @@ -115,6 +115,18 @@ public class PostgreSQLEnvironment extends JDBCEnvironment { * The default value to use for SSL mode if none is explicitly configured. */ private final PostgreSQLSSLMode DEFAULT_SSL_MODE = PostgreSQLSSLMode.PREFER; + + /** + * The default maximum number of identifiers/parameters to be included in a + * single batch when executing SQL statements for PostgreSQL. + * + * PostgreSQL has a maximum limit of 65535 parameters per prepared statement. + * A value of 5000 is chosen to avoid potential performance issues or query + * execution errors while staying well below the maximum limit. + * + * @see https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-MAX-PREPARED-STATEMENT-ARGS + */ + private static final int DEFAULT_BATCH_SIZE = 5000; /** * Constructs a new PostgreSQLEnvironment, providing access to PostgreSQL-specific @@ -146,6 +158,13 @@ public class PostgreSQLEnvironment extends JDBCEnvironment { ); } + @Override + public int getBatchSize() throws GuacamoleException { + return getProperty(PostgreSQLGuacamoleProperties.POSTGRESQL_BATCH_SIZE, + DEFAULT_BATCH_SIZE + ); + } + @Override public int getDefaultMaxConnections() throws GuacamoleException { return getProperty( diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/java/org/apache/guacamole/auth/postgresql/conf/PostgreSQLGuacamoleProperties.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/java/org/apache/guacamole/auth/postgresql/conf/PostgreSQLGuacamoleProperties.java index 271d9c0dd..cd6912e74 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/java/org/apache/guacamole/auth/postgresql/conf/PostgreSQLGuacamoleProperties.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-postgresql/src/main/java/org/apache/guacamole/auth/postgresql/conf/PostgreSQLGuacamoleProperties.java @@ -277,4 +277,16 @@ public class PostgreSQLGuacamoleProperties { }; + /** + * The maximum number of identifiers/parameters to be included in a single batch when + * executing SQL statements. + */ + public static final IntegerGuacamoleProperty POSTGRESQL_BATCH_SIZE = + new IntegerGuacamoleProperty() { + + @Override + public String getName() { return "postgresql-batch-size"; } + + }; + } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/conf/SQLServerEnvironment.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/conf/SQLServerEnvironment.java index 0e1554371..ba383131a 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/conf/SQLServerEnvironment.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/conf/SQLServerEnvironment.java @@ -88,6 +88,17 @@ public class SQLServerEnvironment extends JDBCEnvironment { */ public static final SQLServerDriver SQLSERVER_DEFAULT_DRIVER = SQLServerDriver.MICROSOFT_2005; + /** + * The default maximum number of identifiers/parameters to be included in a + * single batch when executing SQL statements for SQL Server. + * + * SQL Server supports a maximum of 2100 parameters per query. A value of + * 1000 is chosen to stay within this limit and avoid query execution errors. + * + * @see https://docs.microsoft.com/en-us/sql/sql-server/maximum-capacity-specifications-for-sql-server + */ + private static final int DEFAULT_BATCH_SIZE = 1000; + /** * Constructs a new SQLServerEnvironment, providing access to SQLServer-specific * configuration options. @@ -117,6 +128,13 @@ public class SQLServerEnvironment extends JDBCEnvironment { DEFAULT_ABSOLUTE_MAX_CONNECTIONS ); } + + @Override + public int getBatchSize() throws GuacamoleException { + return getProperty(SQLServerGuacamoleProperties.SQLSERVER_BATCH_SIZE, + DEFAULT_BATCH_SIZE + ); + } @Override public int getDefaultMaxConnections() throws GuacamoleException { diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/conf/SQLServerGuacamoleProperties.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/conf/SQLServerGuacamoleProperties.java index df63c53e9..10c8075d6 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/conf/SQLServerGuacamoleProperties.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-sqlserver/src/main/java/org/apache/guacamole/auth/sqlserver/conf/SQLServerGuacamoleProperties.java @@ -206,5 +206,17 @@ public class SQLServerGuacamoleProperties { public String getName() { return "sqlserver-auto-create-accounts"; } }; + + /** + * The maximum number of identifiers/parameters to be included in a single batch when + * executing SQL statements. + */ + public static final IntegerGuacamoleProperty SQLSERVER_BATCH_SIZE = + new IntegerGuacamoleProperty() { + + @Override + public String getName() { return "sqlserver-batch-size"; } + + }; }