From 29d44809e767096c63d291ec8731c035c6796f82 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Mon, 7 Sep 2026 14:35:47 -0700 Subject: [PATCH 1/6] Update string literal escaping for Redshift --- .../data/dialect/BasePostgreSqlDialect.java | 18 +++++++++++++++--- .../core/dialect/PostgreSql92Dialect.java | 12 ------------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/api/src/org/labkey/api/data/dialect/BasePostgreSqlDialect.java b/api/src/org/labkey/api/data/dialect/BasePostgreSqlDialect.java index 1bfa267bfb4..d69389f621c 100644 --- a/api/src/org/labkey/api/data/dialect/BasePostgreSqlDialect.java +++ b/api/src/org/labkey/api/data/dialect/BasePostgreSqlDialect.java @@ -94,9 +94,21 @@ public abstract class BasePostgreSqlDialect extends SqlDialect public boolean getStandardConformingStrings() { - // make sure we're not calling this before finishing instance init - assert _standardConformingStrings != null; - return _standardConformingStrings == null || _standardConformingStrings; + // This should always be set before prior to getting the value + if (_standardConformingStrings == null) + throw new IllegalStateException("Standard Conforming Strings are not set for " + this); + return _standardConformingStrings; + } + + + @Override + protected DialectStringHandler createStringHandler() + { + // TODO: Should we look at "backslash_quote" setting instead/in addition? + if (getStandardConformingStrings()) + return super.createStringHandler(); + else + return new BackslashEscapingStringHandler(); } public void setStandardConformingStrings(boolean standardConformingStrings) diff --git a/core/src/org/labkey/core/dialect/PostgreSql92Dialect.java b/core/src/org/labkey/core/dialect/PostgreSql92Dialect.java index 9ca0dd81a1a..0d3fcaa62fd 100644 --- a/core/src/org/labkey/core/dialect/PostgreSql92Dialect.java +++ b/core/src/org/labkey/core/dialect/PostgreSql92Dialect.java @@ -38,9 +38,7 @@ import org.labkey.api.data.TableInfo; import org.labkey.api.data.TempTableInClauseGenerator; import org.labkey.api.data.TempTableTracker; -import org.labkey.api.data.dialect.BackslashEscapingStringHandler; import org.labkey.api.data.dialect.BasePostgreSqlDialect; -import org.labkey.api.data.dialect.DialectStringHandler; import org.labkey.api.data.dialect.JdbcHelper; import org.labkey.api.data.dialect.SqlDialect; import org.labkey.api.data.dialect.StandardJdbcHelper; @@ -184,16 +182,6 @@ protected void determineSettings(DbScope scope) } } - @Override - protected DialectStringHandler createStringHandler() - { - // TODO: Isn't this the wrong setting? Should we be looking at the "backslash_quote" setting instead? - if (getStandardConformingStrings()) - return super.createStringHandler(); - else - return new BackslashEscapingStringHandler(); - } - /* PostgreSQL example connection URLs we need to parse: From 57cf1e063b406a3c4039b95601c49a6f28d0730d Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Mon, 7 Sep 2026 15:18:38 -0700 Subject: [PATCH 2/6] Move more PostgreSQL-specific functionality out of base dialect: user-defined types / domains, standard confirming strings setting, server type --- .../data/dialect/BasePostgreSqlDialect.java | 158 +----------------- .../core/dialect/PostgreSql92Dialect.java | 148 +++++++++++++++- .../dialect/PostgreSqlDialectFactory.java | 1 - .../core}/dialect/PostgreSqlServerType.java | 2 +- 4 files changed, 149 insertions(+), 160 deletions(-) rename {api/src/org/labkey/api/data => core/src/org/labkey/core}/dialect/PostgreSqlServerType.java (97%) diff --git a/api/src/org/labkey/api/data/dialect/BasePostgreSqlDialect.java b/api/src/org/labkey/api/data/dialect/BasePostgreSqlDialect.java index d69389f621c..27afa2bf9a2 100644 --- a/api/src/org/labkey/api/data/dialect/BasePostgreSqlDialect.java +++ b/api/src/org/labkey/api/data/dialect/BasePostgreSqlDialect.java @@ -21,14 +21,12 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.labkey.api.collections.CaseInsensitiveMapWrapper; -import org.labkey.api.collections.CopyOnWriteHashMap; import org.labkey.api.collections.CsvSet; import org.labkey.api.collections.Sets; import org.labkey.api.data.ColumnInfo; import org.labkey.api.data.ConnectionWrapper; import org.labkey.api.data.ConnectionWrapper.Closer; import org.labkey.api.data.DatabaseIdentifier; -import org.labkey.api.data.DbSchema; import org.labkey.api.data.DbScope; import org.labkey.api.data.DbScope.LabKeyDataSource; import org.labkey.api.data.ExceptionFramework; @@ -37,16 +35,13 @@ import org.labkey.api.data.PropertyStorageSpec; import org.labkey.api.data.RuntimeSQLException; import org.labkey.api.data.SQLFragment; -import org.labkey.api.data.Selector; import org.labkey.api.data.SqlExecutingSelector.ConnectionFactory; import org.labkey.api.data.SqlExecutor; -import org.labkey.api.data.SqlSelector; import org.labkey.api.data.Table; import org.labkey.api.data.TableInfo; import org.labkey.api.data.dialect.LimitRowsSqlGenerator.LimitRowsCustomizer; import org.labkey.api.data.dialect.LimitRowsSqlGenerator.StandardLimitRowsCustomizer; import org.labkey.api.exp.PropertyType; -import org.labkey.api.util.ExceptionUtil; import org.labkey.api.util.HtmlString; import org.labkey.api.util.StringUtilsLabKey; import org.labkey.api.view.template.Warnings; @@ -78,54 +73,11 @@ public abstract class BasePostgreSqlDialect extends SqlDialect public static final String POSTGRES_LOCKS_TABLE_NAME = "pg_locks"; public static final String POSTGRES_TABLE_SIZES_TABLE_NAME = "pg_tablesizes"; - private final Map _domainScaleMap = new CopyOnWriteHashMap<>(); - private HtmlString _adminWarning = null; // Default to 9 and let newer versions be refreshed later private int _majorVersion = 9; - // Specifies if this PostgreSQL server treats backslashes in string literals as normal characters (as per the SQL - // standard) or as escape characters (old, non-standard behavior). As of PostgreSQL 9.1, the setting - // standard_conforming_strings is on by default; before 9.1, it was off by default. We check the server setting - // when we prepare a new DbScope and use this when we escape and parse string literals. - private Boolean _standardConformingStrings = Boolean.TRUE; - private PostgreSqlServerType _serverType = PostgreSqlServerType.PostgreSQL; - - public boolean getStandardConformingStrings() - { - // This should always be set before prior to getting the value - if (_standardConformingStrings == null) - throw new IllegalStateException("Standard Conforming Strings are not set for " + this); - return _standardConformingStrings; - } - - - @Override - protected DialectStringHandler createStringHandler() - { - // TODO: Should we look at "backslash_quote" setting instead/in addition? - if (getStandardConformingStrings()) - return super.createStringHandler(); - else - return new BackslashEscapingStringHandler(); - } - - public void setStandardConformingStrings(boolean standardConformingStrings) - { - _standardConformingStrings = standardConformingStrings; - } - - public PostgreSqlServerType getServerType() - { - return _serverType; - } - - public void setServerType(PostgreSqlServerType serverType) - { - _serverType = serverType; - } - @Override protected @NotNull Set getReservedWords() { @@ -629,69 +581,11 @@ public void prepare(LabKeyDataSource dataSource) } } - @Override - public String prepare(DbScope scope) - { - initializeUserDefinedTypes(scope); - determineSettings(scope); - return super.prepare(scope); - } - @Override public void prepareConnection(Connection conn) { } - // When a new PostgreSQL DbScope is created, we enumerate the domains (user-defined types) in the public schema - // of the datasource, determine their "scale," and stash that information in a map associated with the DbScope. - // When the PostgreSQLColumnMetaDataReader reads metadata, it returns these scale values for all domains. - private void initializeUserDefinedTypes(DbScope scope) - { - // Skip domains query if connecting to LabKey Server - it has no user-defined types - if (getServerType().supportsSpecialMetadataQueries()) - { - Selector selector = new SqlSelector(scope, "SELECT * FROM information_schema.domains"); - selector.forEach(rs -> { - String schemaName = rs.getString("domain_schema"); - String domainName = rs.getString("domain_name"); - String dataType = rs.getString("data_type"); - int scale; - - if (dataType.startsWith("character")) - { - String maxLength = rs.getString("character_maximum_length"); - - // VARCHAR with no specific size has null maxLength... but character_octet_length seems okay - scale = Integer.parseInt(null != maxLength ? maxLength : rs.getString("character_octet_length")); - } - else - { - // Assume everything else is an integer for now. We should support more types for better external schema handling. - scale = 4; - } - - String key = getDomainKey(schemaName, domainName); - _domainScaleMap.put(key, scale); - }); - } - } - - private String getDomainKey(String schemaName, String domainName) - { - // Domain names are returned from column metadata fully qualified and quoted, so save them that way. See #26149. - return ("public".equals(schemaName) ? domainName : "\"" + schemaName + "\".\"" + domainName + "\""); - } - - // Query any settings that may affect dialect behavior. Right now, only "standard_conforming_strings". - protected void determineSettings(DbScope scope) - { - if (getServerType().supportsSpecialMetadataQueries()) - { - Selector selector = new SqlSelector(scope, "SELECT setting FROM pg_settings WHERE name = 'standard_conforming_strings'"); - _standardConformingStrings = "on".equalsIgnoreCase(selector.getObject(String.class)); - } - } - /** * Wrap one or more INSERT statements to allow explicit specification * of values for autoincrementing columns (e.g. IDENTITY in SQL Server @@ -854,7 +748,6 @@ public boolean isEditable() @Override public ColumnMetaDataReader getColumnMetaDataReader(ResultSet rsCols, TableInfo table) { - // Retrieve and pass in the previously queried scale values for this scope. return new PostgreSqlColumnMetaDataReader(rsCols, table); } @@ -1006,9 +899,9 @@ public SQLFragment isNumericExpr(SQLFragment expression) .append(") AS TEXT) ~ '^[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)$' THEN 1 ELSE 0 END)"); } - private class PostgreSqlColumnMetaDataReader extends ColumnMetaDataReader + public static class PostgreSqlColumnMetaDataReader extends ColumnMetaDataReader { - private final TableInfo _table; + protected final TableInfo _table; public PostgreSqlColumnMetaDataReader(ResultSet rsCols, TableInfo table) { @@ -1048,39 +941,6 @@ public int getSqlType() throws SQLException return sqlType; } - @Override - public int getScale() throws SQLException - { - int sqlType = super.getSqlType(); - - return Types.DISTINCT == sqlType ? getDomainScale(getSqlTypeName()) : super.getScale(); - } - - private int getDomainScale(String domainName) throws SQLException - { - Integer scale = _domainScaleMap.get(domainName); - - if (null == scale) - { - // Some domain wasn't there when we initialized the datasource, so reload now. This will happen at bootstrap. - DbSchema schema = _table.getSchema(); - initializeUserDefinedTypes(schema.getScope()); - scale = _domainScaleMap.get(domainName); - - // If scale is still null, then we have a problem. We've seen occasional exception reports showing this, - // but haven't had the information to track it down... so log additional info. - if (null == scale) - { - String message = "Null scale for \"" + domainName + "\" in column \"" + _table.getName() + "." + getName() + "\" in schema \"" + schema.getName() + "\""; - ExceptionUtil.logExceptionToMothership(null, new Exception(message)); - assert false : message; - return 4; // Return something on production servers so schema can continue to load - } - } - - return scale; - } - @Nullable @Override public String getDefault() throws SQLException @@ -1292,20 +1152,6 @@ public void addAdminWarningMessages(Warnings warnings, boolean showAllWarnings) warnings.add(_adminWarning); } - @Override - public boolean isProcedureExists(DbScope scope, String schema, String name) - { - // Don't bother querying LabKey for stored procedures - return getServerType().supportsSpecialMetadataQueries() && super.isProcedureExists(scope, schema, name); - } - - @Override - public boolean shouldTest() - { - // Don't test a LabKey data source - return getServerType().shouldTest(); - } - @Override public @Nullable String getApplicationNameParameter() { diff --git a/core/src/org/labkey/core/dialect/PostgreSql92Dialect.java b/core/src/org/labkey/core/dialect/PostgreSql92Dialect.java index 0d3fcaa62fd..5c38a694448 100644 --- a/core/src/org/labkey/core/dialect/PostgreSql92Dialect.java +++ b/core/src/org/labkey/core/dialect/PostgreSql92Dialect.java @@ -19,6 +19,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Strings; import org.jetbrains.annotations.NotNull; +import org.labkey.api.collections.CopyOnWriteHashMap; import org.labkey.api.data.Constraint; import org.labkey.api.data.CoreSchema; import org.labkey.api.data.DatabaseIdentifier; @@ -38,13 +39,17 @@ import org.labkey.api.data.TableInfo; import org.labkey.api.data.TempTableInClauseGenerator; import org.labkey.api.data.TempTableTracker; +import org.labkey.api.data.dialect.BackslashEscapingStringHandler; import org.labkey.api.data.dialect.BasePostgreSqlDialect; +import org.labkey.api.data.dialect.ColumnMetaDataReader; +import org.labkey.api.data.dialect.DialectStringHandler; import org.labkey.api.data.dialect.JdbcHelper; import org.labkey.api.data.dialect.SqlDialect; import org.labkey.api.data.dialect.StandardJdbcHelper; import org.labkey.api.exp.PropertyType; import org.labkey.api.query.AliasManager; import org.labkey.api.util.ConfigurationException; +import org.labkey.api.util.ExceptionUtil; import org.labkey.api.util.HtmlString; import org.labkey.api.util.StringUtilsLabKey; import org.labkey.api.view.template.Warnings; @@ -56,6 +61,7 @@ import java.sql.Driver; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Types; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -88,6 +94,128 @@ abstract class PostgreSql92Dialect extends BasePostgreSqlDialect private final TempTableInClauseGenerator _tempTableInClauseGenerator = new TempTableInClauseGenerator(); private final AtomicBoolean _arraySortFunctionExists = new AtomicBoolean(false); + // Specifies if this PostgreSQL server treats backslashes in string literals as normal characters (as per the SQL + // standard) or as escape characters (old, non-standard behavior). As of PostgreSQL 9.1, the setting + // standard_conforming_strings is on by default; before 9.1, it was off by default. We check the server setting + // when we prepare a new DbScope and use this when we escape and parse string literals. + private Boolean _standardConformingStrings = Boolean.TRUE; + private PostgreSqlServerType _serverType = PostgreSqlServerType.PostgreSQL; + private final Map _domainScaleMap = new CopyOnWriteHashMap<>(); + + public boolean getStandardConformingStrings() + { + // This should always be set before prior to getting the value + if (_standardConformingStrings == null) + throw new IllegalStateException("Standard Conforming Strings are not set for " + this); + return _standardConformingStrings; + } + + @Override + protected DialectStringHandler createStringHandler() + { + // TODO: Should we look at "backslash_quote" setting instead/in addition? + if (getStandardConformingStrings()) + return super.createStringHandler(); + else + return new BackslashEscapingStringHandler(); + } + + public void setStandardConformingStrings(boolean standardConformingStrings) + { + _standardConformingStrings = standardConformingStrings; + } + + public PostgreSqlServerType getServerType() + { + return _serverType; + } + + public void setServerType(PostgreSqlServerType serverType) + { + _serverType = serverType; + } + + // When a new PostgreSQL DbScope is created, we enumerate the domains (user-defined types) in the public schema + // of the datasource, determine their "scale," and stash that information in a map associated with the DbScope. + // When the PostgreSQLColumnMetaDataReader reads metadata, it returns these scale values for all domains. + private void initializeUserDefinedTypes(DbScope scope) + { + // Skip domains query if connecting to LabKey Server - it has no user-defined types + if (getServerType().supportsSpecialMetadataQueries()) + { + Selector selector = new SqlSelector(scope, "SELECT * FROM information_schema.domains"); + selector.forEach(rs -> { + String schemaName = rs.getString("domain_schema"); + String domainName = rs.getString("domain_name"); + String dataType = rs.getString("data_type"); + int scale; + + if (dataType.startsWith("character")) + { + String maxLength = rs.getString("character_maximum_length"); + + // VARCHAR with no specific size has null maxLength... but character_octet_length seems okay + scale = Integer.parseInt(null != maxLength ? maxLength : rs.getString("character_octet_length")); + } + else + { + // Assume everything else is an integer for now. We should support more types for better external schema handling. + scale = 4; + } + + String key = getDomainKey(schemaName, domainName); + _domainScaleMap.put(key, scale); + }); + } + } + + private String getDomainKey(String schemaName, String domainName) + { + // Domain names are returned from column metadata fully qualified and quoted, so save them that way. See #26149. + return ("public".equals(schemaName) ? domainName : "\"" + schemaName + "\".\"" + domainName + "\""); + } + + @Override + public ColumnMetaDataReader getColumnMetaDataReader(ResultSet rsCols, TableInfo table) + { + // Subclass that supports PostgreSQL-specific domains / user-defined types + return new PostgreSqlColumnMetaDataReader(rsCols, table) + { + @Override + public int getScale() throws SQLException + { + int sqlType = super.getSqlType(); + + return Types.DISTINCT == sqlType ? getDomainScale(getSqlTypeName()) : super.getScale(); + } + + private int getDomainScale(String domainName) throws SQLException + { + Integer scale = _domainScaleMap.get(domainName); + + if (null == scale) + { + // Some domain wasn't there when we initialized the datasource, so reload now. This will happen at bootstrap. + DbSchema schema = _table.getSchema(); + initializeUserDefinedTypes(schema.getScope()); + scale = _domainScaleMap.get(domainName); + + // If scale is still null, then we have a problem. We've seen occasional exception reports showing this, + // but haven't had the information to track it down... so log additional info. + if (null == scale) + { + String message = "Null scale for \"" + domainName + "\" in column \"" + _table.getName() + "." + getName() + "\" in schema \"" + schema.getName() + "\""; + ExceptionUtil.logExceptionToMothership(null, new Exception(message)); + assert false : message; + return 4; // Return something on production servers so schema can continue to load + } + } + + return scale; + } + }; + } + @Override public void handleCreateDatabaseException(SQLException e) throws ServletException { @@ -134,6 +262,8 @@ public String prepare(DbScope scope) { initializeInClauseGenerator(scope); determineIfArraySortFunctionExists(scope); + initializeUserDefinedTypes(scope); + determineSettings(scope); return super.prepare(scope); } @@ -163,12 +293,12 @@ public String getProductName() } // Query PostgreSQL-specific settings - @Override protected void determineSettings(DbScope scope) { if (getServerType().supportsSpecialMetadataQueries()) { - super.determineSettings(scope); + Selector selector = new SqlSelector(scope, "SELECT setting FROM pg_settings WHERE name = 'standard_conforming_strings'"); + _standardConformingStrings = "on".equalsIgnoreCase(selector.getObject(String.class)); String value = new SqlSelector(scope, "SELECT setting FROM pg_settings WHERE name = 'max_identifier_length'").getObject(String.class); try @@ -182,6 +312,20 @@ protected void determineSettings(DbScope scope) } } + @Override + public boolean isProcedureExists(DbScope scope, String schema, String name) + { + // Don't bother querying LabKey for stored procedures + return getServerType().supportsSpecialMetadataQueries() && super.isProcedureExists(scope, schema, name); + } + + @Override + public boolean shouldTest() + { + // Don't test a LabKey data source + return getServerType().shouldTest(); + } + /* PostgreSQL example connection URLs we need to parse: diff --git a/core/src/org/labkey/core/dialect/PostgreSqlDialectFactory.java b/core/src/org/labkey/core/dialect/PostgreSqlDialectFactory.java index 382f4264746..5620c3977b9 100644 --- a/core/src/org/labkey/core/dialect/PostgreSqlDialectFactory.java +++ b/core/src/org/labkey/core/dialect/PostgreSqlDialectFactory.java @@ -27,7 +27,6 @@ import org.labkey.api.data.dialect.BasePostgreSqlDialect; import org.labkey.api.data.dialect.DatabaseNotSupportedException; import org.labkey.api.data.dialect.JdbcHelperTest; -import org.labkey.api.data.dialect.PostgreSqlServerType; import org.labkey.api.data.dialect.SqlDialect; import org.labkey.api.data.dialect.SqlDialectFactory; import org.labkey.api.data.dialect.TestUpgradeCodeCounter; diff --git a/api/src/org/labkey/api/data/dialect/PostgreSqlServerType.java b/core/src/org/labkey/core/dialect/PostgreSqlServerType.java similarity index 97% rename from api/src/org/labkey/api/data/dialect/PostgreSqlServerType.java rename to core/src/org/labkey/core/dialect/PostgreSqlServerType.java index 2fd04575c68..67fabd19404 100644 --- a/api/src/org/labkey/api/data/dialect/PostgreSqlServerType.java +++ b/core/src/org/labkey/core/dialect/PostgreSqlServerType.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.labkey.api.data.dialect; +package org.labkey.core.dialect; import java.util.Map; From 3f4ff556555476af097a0790e451c515d7396f86 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Mon, 7 Sep 2026 15:31:58 -0700 Subject: [PATCH 3/6] Override createStringHandler() instead --- api/src/org/labkey/api/data/dialect/SqlDialect.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/src/org/labkey/api/data/dialect/SqlDialect.java b/api/src/org/labkey/api/data/dialect/SqlDialect.java index 805624000bf..4610110eb4f 100644 --- a/api/src/org/labkey/api/data/dialect/SqlDialect.java +++ b/api/src/org/labkey/api/data/dialect/SqlDialect.java @@ -482,7 +482,8 @@ protected DialectStringHandler createStringHandler() return new StandardDialectStringHandler(); } - public synchronized DialectStringHandler getStringHandler() + // Override createStringHandler() instead + public final synchronized DialectStringHandler getStringHandler() { if (null == _stringHandler) _stringHandler = createStringHandler(); From 35909f142bc792da7bf61b2b490c75bd0c587239 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Mon, 7 Sep 2026 15:47:40 -0700 Subject: [PATCH 4/6] Move a couple more PostgreSQL-specific methods --- .../api/data/dialect/BasePostgreSqlDialect.java | 12 ------------ .../org/labkey/core/dialect/PostgreSql92Dialect.java | 12 ++++++++++++ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/api/src/org/labkey/api/data/dialect/BasePostgreSqlDialect.java b/api/src/org/labkey/api/data/dialect/BasePostgreSqlDialect.java index 27afa2bf9a2..95b21ba9539 100644 --- a/api/src/org/labkey/api/data/dialect/BasePostgreSqlDialect.java +++ b/api/src/org/labkey/api/data/dialect/BasePostgreSqlDialect.java @@ -100,12 +100,6 @@ public StatementWrapper getStatementWrapper(ConnectionWrapper conn, Statement st return statementWrapper; } - @Override - public SQLFragment getDatabaseSizeSql(String databaseName) - { - return new SQLFragment("SELECT pg_database_size(?)", databaseName); - } - @Override public boolean cancelQueries(DbScope scope, Collection connections, boolean terminate) { @@ -422,12 +416,6 @@ public SQLFragment wrapBooleanExpression(SQLFragment booleanSql) return booleanSql; } - @Override - protected String getSystemTableNames() - { - return "pg_logdir_ls"; - } - @Override public boolean isSystemSchema(String schemaName) { diff --git a/core/src/org/labkey/core/dialect/PostgreSql92Dialect.java b/core/src/org/labkey/core/dialect/PostgreSql92Dialect.java index 5c38a694448..48f493cebac 100644 --- a/core/src/org/labkey/core/dialect/PostgreSql92Dialect.java +++ b/core/src/org/labkey/core/dialect/PostgreSql92Dialect.java @@ -292,6 +292,18 @@ public String getProductName() return PRODUCT_NAME; } + @Override + protected String getSystemTableNames() + { + return "pg_logdir_ls"; + } + + @Override + public SQLFragment getDatabaseSizeSql(String databaseName) + { + return new SQLFragment("SELECT pg_database_size(?)", databaseName); + } + // Query PostgreSQL-specific settings protected void determineSettings(DbScope scope) { From a3576e61c2c0ad78409b4041becceb5b81a63cd4 Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Mon, 7 Sep 2026 16:02:24 -0700 Subject: [PATCH 5/6] Move more PostgreSQL-specific methods --- .../data/dialect/BasePostgreSqlDialect.java | 21 +++---------------- .../core/dialect/PostgreSql92Dialect.java | 17 +++++++++++++++ 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/api/src/org/labkey/api/data/dialect/BasePostgreSqlDialect.java b/api/src/org/labkey/api/data/dialect/BasePostgreSqlDialect.java index 95b21ba9539..61ad4f9ffba 100644 --- a/api/src/org/labkey/api/data/dialect/BasePostgreSqlDialect.java +++ b/api/src/org/labkey/api/data/dialect/BasePostgreSqlDialect.java @@ -66,7 +66,9 @@ // if not, put it in PostgreSql92Dialect. public abstract class BasePostgreSqlDialect extends SqlDialect { - // Issue 52190: Expose troubleshooting data that supports postgreSQL-specific analysis + // Issue 52190: Expose troubleshooting data that supports postgreSQL-specific analysis. These names are also used + // by org.labkey.api.util.DebugInfoDumper, so they must stay in the api module even though the queries they back + // (see PostgreSql92Dialect) are Postgres-only, not Redshift. public static final String POSTGRES_SCHEMA_NAME = "postgres"; public static final String POSTGRES_STAT_ACTIVITY_TABLE_NAME = "pg_stat_activity"; @@ -944,17 +946,6 @@ public PkMetaDataReader getPkMetaDataReader(ResultSet rs) return new PkMetaDataReader(rs, "COLUMN_NAME", "KEY_SEQ"); } - @Override - public String getExtraInfo(SQLException e) - { - // Deadlock between two different DB connections - if ("40P01".equals(e.getSQLState())) - { - return getOtherDatabaseThreads(); - } - return null; - } - @Override public ConnectionFactory getConnectionFactory(boolean useJdbcCaching, boolean selfContained, DbScope scope, SQLFragment sql) { @@ -1157,10 +1148,4 @@ public void addAdminWarningMessages(Warnings warnings, boolean showAllWarnings) { return "PostgreSQL JDBC Driver"; } - - @Override - public @NotNull String getApplicationConnectionsSql() - { - return "SELECT pid, usename, client_addr, client_hostname, xact_start, query_start, state, application_name, query FROM pg_stat_activity WHERE pid <> pg_backend_pid() AND datname = ? AND application_name = ?"; - } } diff --git a/core/src/org/labkey/core/dialect/PostgreSql92Dialect.java b/core/src/org/labkey/core/dialect/PostgreSql92Dialect.java index 48f493cebac..2c217c50058 100644 --- a/core/src/org/labkey/core/dialect/PostgreSql92Dialect.java +++ b/core/src/org/labkey/core/dialect/PostgreSql92Dialect.java @@ -304,6 +304,23 @@ public SQLFragment getDatabaseSizeSql(String databaseName) return new SQLFragment("SELECT pg_database_size(?)", databaseName); } + @Override + public String getExtraInfo(SQLException e) + { + // Deadlock between two different DB connections + if ("40P01".equals(e.getSQLState())) + { + return getOtherDatabaseThreads(); + } + return null; + } + + @Override + public @NotNull String getApplicationConnectionsSql() + { + return "SELECT pid, usename, client_addr, client_hostname, xact_start, query_start, state, application_name, query FROM pg_stat_activity WHERE pid <> pg_backend_pid() AND datname = ? AND application_name = ?"; + } + // Query PostgreSQL-specific settings protected void determineSettings(DbScope scope) { From 6ab4b0f792c2666295f38cb0818857d93347c44b Mon Sep 17 00:00:00 2001 From: Adam Rauch Date: Mon, 7 Sep 2026 16:52:53 -0700 Subject: [PATCH 6/6] Snowflake escaping --- .../BackslashEscapingStringHandler.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/api/src/org/labkey/api/data/dialect/BackslashEscapingStringHandler.java b/api/src/org/labkey/api/data/dialect/BackslashEscapingStringHandler.java index 150c37a827d..342f6f32541 100644 --- a/api/src/org/labkey/api/data/dialect/BackslashEscapingStringHandler.java +++ b/api/src/org/labkey/api/data/dialect/BackslashEscapingStringHandler.java @@ -17,6 +17,9 @@ package org.labkey.api.data.dialect; import org.apache.commons.lang3.Strings; +import org.junit.Assert; +import org.junit.Test; +import org.labkey.api.data.SQLFragment; // Adds support for backslash escaping in string literals public class BackslashEscapingStringHandler extends StandardDialectStringHandler @@ -56,4 +59,25 @@ else if (c == '\'') return current; } + + public static abstract class BackslashEscapingStringHandlerTestCase extends Assert + { + protected abstract SqlDialect getSqlDialect(); + + @Test + public void testAppendLiteral() + { + SqlDialect dialect = getSqlDialect(); + testAppendLiteral(dialect, "\\", "'\\\\'"); + testAppendLiteral(dialect, "C:\\Users\\Name\\Documents", "'C:\\\\Users\\\\Name\\\\Documents'"); + testAppendLiteral(dialect, "What's the buzz, tell me what's happening", "'What''s the buzz, tell me what''s happening'"); + testAppendLiteral(dialect, "\\d+", "'\\\\d+'"); + } + + private void testAppendLiteral(SqlDialect dialect , String literal, String expected) + { + String actual = new SQLFragment().appendStringLiteral(literal, dialect).toDebugString(dialect); + assertEquals(expected, actual); + } + } }