diff --git a/src/NHibernate.Test/MappingTest/ColumnFixture.cs b/src/NHibernate.Test/MappingTest/ColumnFixture.cs index 94f6a8c4c12..3363fb1675c 100644 --- a/src/NHibernate.Test/MappingTest/ColumnFixture.cs +++ b/src/NHibernate.Test/MappingTest/ColumnFixture.cs @@ -40,6 +40,8 @@ public void StringSqlType() Assert.AreEqual("NVARCHAR(100)", column.GetSqlType(_dialect, null)); } + // Should be kept synchronized with Column same constant. + private const int _charactersLeftCount = 4; [TestCase("xxxxyyyyz")] [TestCase("xxxxyyyyzz")] @@ -54,16 +56,15 @@ public void GetAliasRespectsMaxAliasLength(string columnName) { var dialect = new GenericDialect(); - // Verify test case assumption. - Assert.That(dialect.MaxAliasLength, Is.EqualTo(10)); + // Test case is meant for a max length of 10, adjusts name if it is more. + columnName = AdjustColumnNameToMaxLength(columnName, dialect, 10); var column = new Column(columnName); string generatedAlias = column.GetAlias(dialect); - Assert.That(generatedAlias, Has.Length.LessThanOrEqualTo(dialect.MaxAliasLength)); + Assert.That(generatedAlias, Has.Length.LessThanOrEqualTo(dialect.MaxAliasLength - _charactersLeftCount)); } - [TestCase("xxxxyyyyz")] [TestCase("xxxxyyyyzz")] [TestCase("xxxxyyyyzzz")] @@ -77,15 +78,24 @@ public void GetAliasWithTableSuffixRespectsMaxAliasLength(string columnName) { var dialect = new GenericDialect(); - // Verify test case assumption. - Assert.That(dialect.MaxAliasLength, Is.EqualTo(10)); + // Test case is meant for a max length of 10, adjusts name if it is more. + columnName = AdjustColumnNameToMaxLength(columnName, dialect, 10); var table = new Table(); var column = new Column(columnName); string generatedAlias = column.GetAlias(dialect, table); - Assert.That(generatedAlias, Has.Length.LessThanOrEqualTo(dialect.MaxAliasLength)); + Assert.That(generatedAlias, Has.Length.LessThanOrEqualTo(dialect.MaxAliasLength - _charactersLeftCount)); + } + + private static string AdjustColumnNameToMaxLength(string columnName, GenericDialect dialect, int referenceMaxLength) + { + if (dialect.MaxAliasLength > referenceMaxLength) + columnName = new string('w', dialect.MaxAliasLength - referenceMaxLength) + columnName; + else if (dialect.MaxAliasLength < referenceMaxLength) + Assert.Fail("Dialect max alias length is too short for the test."); + return columnName; } } } diff --git a/src/NHibernate.sln.DotSettings b/src/NHibernate.sln.DotSettings index 918ecb514b5..a38b0a203a1 100644 --- a/src/NHibernate.sln.DotSettings +++ b/src/NHibernate.sln.DotSettings @@ -19,6 +19,7 @@ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True True True True diff --git a/src/NHibernate/Dialect/DB2Dialect.cs b/src/NHibernate/Dialect/DB2Dialect.cs index fc578dcc6df..b9b785a2025 100644 --- a/src/NHibernate/Dialect/DB2Dialect.cs +++ b/src/NHibernate/Dialect/DB2Dialect.cs @@ -272,6 +272,10 @@ public override string ForUpdateString get { return " for read only with rs"; } } + // As of DB2 9.5 documentation, limit is 128 bytes which with Unicode names could mean only 32 characters. + /// + public override int MaxAliasLength => 32; + #region Overridden informational metadata public override bool SupportsEmptyInList => false; diff --git a/src/NHibernate/Dialect/Dialect.cs b/src/NHibernate/Dialect/Dialect.cs index 0cb0d53c605..0a07ed0134c 100644 --- a/src/NHibernate/Dialect/Dialect.cs +++ b/src/NHibernate/Dialect/Dialect.cs @@ -2282,10 +2282,11 @@ public virtual string LowercaseFunction get { return "lower"; } } - public virtual int MaxAliasLength - { - get { return 10; } - } + // 18 is the smallest of all dialects we handle. + /// + /// The maximum length a SQL alias can have. + /// + public virtual int MaxAliasLength => 18; /// /// The syntax used to add a column to a table. Note this is deprecated diff --git a/src/NHibernate/Dialect/FirebirdDialect.cs b/src/NHibernate/Dialect/FirebirdDialect.cs index 564ba59ddab..e84436b5a08 100644 --- a/src/NHibernate/Dialect/FirebirdDialect.cs +++ b/src/NHibernate/Dialect/FirebirdDialect.cs @@ -520,6 +520,12 @@ private void RegisterTrigonometricFunctions() RegisterFunction("tanh", new StandardSQLFunction("tanh", NHibernateUtil.Double)); } + // As of Firebird 2.5 documentation, limit is 30/31 (not all source are concordant), with some + // cases supporting more but considered as bugs and no more tolerated in v3. + // It seems it may be extended to 63 for Firebird v4. + /// + public override int MaxAliasLength => 30; + #region Informational metadata /// diff --git a/src/NHibernate/Dialect/InformixDialect.cs b/src/NHibernate/Dialect/InformixDialect.cs index 94b174b5e64..4bedf931856 100644 --- a/src/NHibernate/Dialect/InformixDialect.cs +++ b/src/NHibernate/Dialect/InformixDialect.cs @@ -457,6 +457,10 @@ public override string GetAddForeignKeyConstraintString(string constraintName, s return res.ToString(); } + + // Informix 7 is said on Internet to be limited to 18. (http://www.justskins.com/forums/length-of-columns-names-143294.html) + /// + public override int MaxAliasLength => 18; } public class IfxViolatedConstraintExtracter : TemplatedViolatedConstraintNameExtracter diff --git a/src/NHibernate/Dialect/InformixDialect0940.cs b/src/NHibernate/Dialect/InformixDialect0940.cs index f80876c0b43..59ef5b7a6e6 100644 --- a/src/NHibernate/Dialect/InformixDialect0940.cs +++ b/src/NHibernate/Dialect/InformixDialect0940.cs @@ -146,5 +146,8 @@ public override bool SupportsLimitOffset get { return false; } } + // Informix 9 is said on Internet to be limited to 128. (http://www.justskins.com/forums/length-of-columns-names-143294.html) + /// + public override int MaxAliasLength => 128; }; -} \ No newline at end of file +} diff --git a/src/NHibernate/Dialect/IngresDialect.cs b/src/NHibernate/Dialect/IngresDialect.cs index 06395686aba..9741642ccde 100644 --- a/src/NHibernate/Dialect/IngresDialect.cs +++ b/src/NHibernate/Dialect/IngresDialect.cs @@ -53,6 +53,15 @@ public IngresDialect() DefaultProperties[Environment.ConnectionDriver] = "NHibernate.Driver.IngresDriver"; } + // Ingres 10.2 supports 256 bytes (so worst unicode case would mean 64 characters), but I am unable to find + // the limit for older versions, excepted many various sites mention a 32 length limit. + // https://unifaceinfo.com/docs/0906/Uniface_Library_HTML/ulibrary/INS_NAMING_RULES_8EEFC1A489331BF969D2A8AA36AF2832.html + // There are traces of a ticket for increasing this in version 10: http://lists.ingres.com/pipermail/bugs/2010-May/000052.html + // This dialect seems to target version below 9, since there is Ingres9Dialect deriving from it. + // So sticking to 32. + /// + public override int MaxAliasLength => 32; + #region Overridden informational metadata public override bool SupportsEmptyInList => false; diff --git a/src/NHibernate/Dialect/MsSql2000Dialect.cs b/src/NHibernate/Dialect/MsSql2000Dialect.cs index 1418beccbda..9fbadeeda95 100644 --- a/src/NHibernate/Dialect/MsSql2000Dialect.cs +++ b/src/NHibernate/Dialect/MsSql2000Dialect.cs @@ -677,6 +677,11 @@ public override bool SupportsSqlBatches get { return true; } } + // Was 30 in "earlier version", without telling to which version the document apply. + // https://msdn.microsoft.com/en-us/library/ms191240.aspx#Anchor_3 + /// + public override int MaxAliasLength => 30; + #region Overridden informational metadata public override bool SupportsEmptyInList => false; diff --git a/src/NHibernate/Dialect/MsSql2005Dialect.cs b/src/NHibernate/Dialect/MsSql2005Dialect.cs index 4e3cd731dde..a53b2248de8 100644 --- a/src/NHibernate/Dialect/MsSql2005Dialect.cs +++ b/src/NHibernate/Dialect/MsSql2005Dialect.cs @@ -103,6 +103,10 @@ public override string AppendLockHint(LockMode lockMode, string tableName) return tableName; } + // SQL Server 2005 supports 128. + /// + public override int MaxAliasLength => 128; + #region Overridden informational metadata /// diff --git a/src/NHibernate/Dialect/MsSqlCeDialect.cs b/src/NHibernate/Dialect/MsSqlCeDialect.cs index 257c71186a1..4b4f4bb9e1b 100644 --- a/src/NHibernate/Dialect/MsSqlCeDialect.cs +++ b/src/NHibernate/Dialect/MsSqlCeDialect.cs @@ -344,6 +344,10 @@ public override long TimestampResolutionInTicks } } + // SQL Server 3.5 supports 128. + /// + public override int MaxAliasLength => 128; + #region Informational metadata /// diff --git a/src/NHibernate/Dialect/MySQL5Dialect.cs b/src/NHibernate/Dialect/MySQL5Dialect.cs index 0a7c73b0018..56724403e53 100644 --- a/src/NHibernate/Dialect/MySQL5Dialect.cs +++ b/src/NHibernate/Dialect/MySQL5Dialect.cs @@ -61,5 +61,10 @@ public override bool SupportsInsertSelectIdentity { get { return true; } } + + // At least MySQL 5 is said to support 64 characters for columns, but 5.7 supports 256 for aliases. + // 64 seems quite good enough, being conservative. + /// + public override int MaxAliasLength => 64; } } diff --git a/src/NHibernate/Dialect/Oracle12cDialect.cs b/src/NHibernate/Dialect/Oracle12cDialect.cs index 38d7c99512b..0603ed43c19 100644 --- a/src/NHibernate/Dialect/Oracle12cDialect.cs +++ b/src/NHibernate/Dialect/Oracle12cDialect.cs @@ -3,7 +3,7 @@ namespace NHibernate.Dialect { /// - /// A dialect specifically for use with Oracle 10g. + /// A dialect specifically for use with Oracle 12c. /// /// /// The main difference between this dialect and @@ -38,5 +38,10 @@ public override SqlString GetLimitString(SqlString querySqlString, SqlString off return result.ToSqlString(); } + + // 128 since 12.2. https://stackoverflow.com/a/756569/1178314, will + // have to do a 12-2c dialect for exploiting it, or wait for 13. + // / + //public override int MaxAliasLength => 128; } -} \ No newline at end of file +} diff --git a/src/NHibernate/Dialect/Oracle8iDialect.cs b/src/NHibernate/Dialect/Oracle8iDialect.cs index ab629fd8e8e..45e15891cc9 100644 --- a/src/NHibernate/Dialect/Oracle8iDialect.cs +++ b/src/NHibernate/Dialect/Oracle8iDialect.cs @@ -533,6 +533,10 @@ public override long TimestampResolutionInTicks } } + // 30 before 12.1. https://stackoverflow.com/a/756569/1178314 + /// + public override int MaxAliasLength => 30; + #region Overridden informational metadata public override bool SupportsEmptyInList diff --git a/src/NHibernate/Dialect/PostgreSQL81Dialect.cs b/src/NHibernate/Dialect/PostgreSQL81Dialect.cs index 05c9592d339..c131396f646 100644 --- a/src/NHibernate/Dialect/PostgreSQL81Dialect.cs +++ b/src/NHibernate/Dialect/PostgreSQL81Dialect.cs @@ -116,5 +116,9 @@ public override bool SupportsInsertSelectIdentity /// public override bool SupportsDateTimeScale => true; + + // Said to be 63 bytes at least since v8. + /// + public override int MaxAliasLength => 63; } } diff --git a/src/NHibernate/Dialect/SQLiteDialect.cs b/src/NHibernate/Dialect/SQLiteDialect.cs index e7dbc859aee..03748e0b022 100644 --- a/src/NHibernate/Dialect/SQLiteDialect.cs +++ b/src/NHibernate/Dialect/SQLiteDialect.cs @@ -395,6 +395,10 @@ public override bool SupportsForeignKeyConstraintInAlterTable /// public override bool SupportsConcurrentWritingConnections => false; + // Said to be unlimited. http://sqlite.1065341.n5.nabble.com/Max-limits-on-the-following-td37859.html + /// + public override int MaxAliasLength => 128; + [Serializable] protected class SQLiteCastFunction : CastFunction { @@ -407,4 +411,4 @@ protected override bool CastingIsRequired(string sqlType) } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Mapping/Column.cs b/src/NHibernate/Mapping/Column.cs index 9ec4e8c18bf..d807125c547 100644 --- a/src/NHibernate/Mapping/Column.cs +++ b/src/NHibernate/Mapping/Column.cs @@ -115,10 +115,14 @@ public string GetQuotedName(Dialect.Dialect d) return IsQuoted ? d.QuoteForColumnName(_name) : _name; } + // Accommodate the one character suffix appended in AbstractCollectionPersister and + // the SelectFragment suffix up to 99 joins. + private const int _charactersLeftCount = 4; /// /// For any column name, generate an alias that is unique to that /// column name, and also take Dialect.MaxAliasLength into account. + /// It keeps four characters left for accommodating additional suffixes. /// public string GetAlias(Dialect.Dialect dialect) { @@ -127,6 +131,7 @@ public string GetAlias(Dialect.Dialect dialect) private string GetAlias(int maxAliasLength) { + var usableLength = maxAliasLength - _charactersLeftCount; var name = CanonicalName; string alias = name; string suffix = UniqueInteger.ToString() + StringHelper.Underscore; @@ -148,25 +153,27 @@ private string GetAlias(int maxAliasLength) // reason, the checks for "_quoted" and "rowid" looks redundant. If you remove // those checks, then the double checks for total length can be reduced to one. // But I will leave it like this for now to make it look similar. /Oskar 2016-08-20 - bool useRawName = name.Length + suffix.Length <= maxAliasLength && + bool useRawName = name.Length + suffix.Length <= usableLength && !_quoted && !StringHelper.EqualsCaseInsensitive(name, "rowid"); if (!useRawName) { - if (suffix.Length >= maxAliasLength) + if (suffix.Length >= usableLength) { throw new MappingException( - string.Format( - "Unique suffix {0} length must be less than maximum {1} characters.", - suffix, - maxAliasLength)); + $"Unique suffix {suffix} length must be less than maximum {usableLength} characters."); } - if (alias.Length + suffix.Length > maxAliasLength) - alias = alias.Substring(0, maxAliasLength - suffix.Length); + if (alias.Length + suffix.Length > usableLength) + alias = alias.Substring(0, usableLength - suffix.Length); } return alias + suffix; } + /// + /// For any column name, generate an alias that is unique to that + /// column name and table, and also take Dialect.MaxAliasLength into account. + /// It keeps four characters left for accommodating additional suffixes. + /// public string GetAlias(Dialect.Dialect dialect, Table table) { string suffix = table.UniqueInteger.ToString() + StringHelper.Underscore; diff --git a/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs b/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs index 528d4230517..42611366c74 100644 --- a/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs +++ b/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs @@ -248,7 +248,11 @@ public AbstractCollectionPersister(Mapping.Collection collection, ICacheConcurre foreach (Column col in collection.Owner.Key.ColumnIterator) { keyColumnNames[k] = col.GetQuotedName(dialect); - keyColumnAliases[k] = col.GetAlias(dialect) + "_owner_"; // Force the alias to be unique in case it conflicts with an alias in the entity + // Force the alias to be unique in case it conflicts with an alias in the entity + // As per Column.GetAlias, we have 3 characters left for SelectFragment suffix and one for here. + // Since suffixes are composed of digits and '_', and GetAlias is already suffixed, adding any other + // letter will avoid collision. + keyColumnAliases[k] = col.GetAlias(dialect) + "o"; k++; } joinColumnNames = new string[collection.Key.ColumnSpan];