diff --git a/QueryBuilder.Tests/Firebird/FirebirdJoinTests.cs b/QueryBuilder.Tests/Firebird/FirebirdJoinTests.cs new file mode 100644 index 00000000..cfa81fae --- /dev/null +++ b/QueryBuilder.Tests/Firebird/FirebirdJoinTests.cs @@ -0,0 +1,35 @@ +using SqlKata.Compilers; +using SqlKata.Tests.Infrastructure; +using Xunit; + +namespace SqlKata.Tests.Firebird +{ + public class FirebirdJoinTests : TestSupport + { + private readonly FirebirdCompiler compiler; + + public FirebirdJoinTests() + { + compiler = Compilers.Get(EngineCodes.Firebird); + } + + [Fact] + public void Join() + { + var query = new Query("Table").Join("TableA", "Column1", "ColumnA"); + var ctx = new SqlResult { Query = query }; + + Assert.Equal("\nINNER JOIN \"TABLEA\" ON \"COLUMN1\" = \"COLUMNA\"", compiler.CompileJoins(ctx)); + } + + + [Fact] + public void JoinWithIndexHint() + { + var query = new Query("Table").Join("TableA", "Column1", "ColumnA", indexHint: "index1"); + var ctx = new SqlResult { Query = query }; + + Assert.Equal("\nINNER JOIN \"TABLEA\" ON \"COLUMN1\" = \"COLUMNA\"", compiler.CompileJoins(ctx)); + } + } +} diff --git a/QueryBuilder.Tests/MySql/MySqlIndexHintTests.cs b/QueryBuilder.Tests/MySql/MySqlIndexHintTests.cs new file mode 100644 index 00000000..a267bd97 --- /dev/null +++ b/QueryBuilder.Tests/MySql/MySqlIndexHintTests.cs @@ -0,0 +1,53 @@ +using SqlKata.Compilers; +using SqlKata.Tests.Infrastructure; +using Xunit; + +namespace SqlKata.Tests.MySql +{ + public class MySqlIndexHintTests : TestSupport + { + private readonly MySqlCompiler compiler; + + public MySqlIndexHintTests() + { + compiler = Compilers.Get(EngineCodes.MySql); + } + + [Fact] + public void WithNoIndexHint() + { + var query = new Query("Table"); + var ctx = new SqlResult { Query = query }; + + Assert.Equal("FROM `Table`", compiler.CompileFrom(ctx)); + } + + [Fact] + public void WithIndexHint() + { + var query = new Query("Table", indexHint: "index1"); + var ctx = new SqlResult { Query = query }; + + Assert.Equal("FROM `Table` USE INDEX(index1)", compiler.CompileFrom(ctx)); + } + + [Fact] + public void JoinWithNoIndexHint() + { + var query = new Query("Table").Join("TableA", "Column1", "ColumnA"); + var ctx = new SqlResult { Query = query }; + + Assert.Equal("\nINNER JOIN `TableA` ON `Column1` = `ColumnA`", compiler.CompileJoins(ctx)); + } + + + [Fact] + public void JoinWithIndexHint() + { + var query = new Query("Table").Join("TableA", "Column1", "ColumnA", indexHint: "index1"); + var ctx = new SqlResult { Query = query }; + + Assert.Equal("\nINNER JOIN `TableA` USE INDEX(index1) ON `Column1` = `ColumnA`", compiler.CompileJoins(ctx)); + } + } +} diff --git a/QueryBuilder.Tests/Oracle/OracleJoinTests.cs b/QueryBuilder.Tests/Oracle/OracleJoinTests.cs new file mode 100644 index 00000000..f40f3859 --- /dev/null +++ b/QueryBuilder.Tests/Oracle/OracleJoinTests.cs @@ -0,0 +1,35 @@ +using SqlKata.Compilers; +using SqlKata.Tests.Infrastructure; +using Xunit; + +namespace SqlKata.Tests.Oracle +{ + public class OracleJoinTests : TestSupport + { + private readonly OracleCompiler compiler; + + public OracleJoinTests() + { + compiler = Compilers.Get(EngineCodes.Oracle); + } + + [Fact] + public void Join() + { + var query = new Query("Table").Join("TableA", "Column1", "ColumnA"); + var ctx = new SqlResult { Query = query }; + + Assert.Equal("\nINNER JOIN \"TableA\" ON \"Column1\" = \"ColumnA\"", compiler.CompileJoins(ctx)); + } + + + [Fact] + public void JoinWithIndexHint() + { + var query = new Query("Table").Join("TableA", "Column1", "ColumnA", indexHint: "index1"); + var ctx = new SqlResult { Query = query }; + + Assert.Equal("\nINNER JOIN \"TableA\" ON \"Column1\" = \"ColumnA\"", compiler.CompileJoins(ctx)); + } + } +} diff --git a/QueryBuilder.Tests/PostgreSql/PostgreServerJoinTests.cs b/QueryBuilder.Tests/PostgreSql/PostgreServerJoinTests.cs new file mode 100644 index 00000000..2eb51ea4 --- /dev/null +++ b/QueryBuilder.Tests/PostgreSql/PostgreServerJoinTests.cs @@ -0,0 +1,35 @@ +using SqlKata.Compilers; +using SqlKata.Tests.Infrastructure; +using Xunit; + +namespace SqlKata.Tests.PostgreSql +{ + public class PostgreServerJoinTests : TestSupport + { + private readonly PostgresCompiler compiler; + + public PostgreServerJoinTests() + { + compiler = Compilers.Get(EngineCodes.PostgreSql); + } + + [Fact] + public void Join() + { + var query = new Query("Table").Join("TableA", "Column1", "ColumnA"); + var ctx = new SqlResult { Query = query }; + + Assert.Equal("\nINNER JOIN \"TableA\" ON \"Column1\" = \"ColumnA\"", compiler.CompileJoins(ctx)); + } + + + [Fact] + public void JoinWithIndexHint() + { + var query = new Query("Table").Join("TableA", "Column1", "ColumnA", indexHint: "index1"); + var ctx = new SqlResult { Query = query }; + + Assert.Equal("\nINNER JOIN \"TableA\" ON \"Column1\" = \"ColumnA\"", compiler.CompileJoins(ctx)); + } + } +} diff --git a/QueryBuilder.Tests/SqlServer/SqlServerJoinTests.cs b/QueryBuilder.Tests/SqlServer/SqlServerJoinTests.cs new file mode 100644 index 00000000..f2c63fa6 --- /dev/null +++ b/QueryBuilder.Tests/SqlServer/SqlServerJoinTests.cs @@ -0,0 +1,35 @@ +using SqlKata.Compilers; +using SqlKata.Tests.Infrastructure; +using Xunit; + +namespace SqlKata.Tests.SqlServer +{ + public class SqlServerJoinTests : TestSupport + { + private readonly SqlServerCompiler compiler; + + public SqlServerJoinTests() + { + compiler = Compilers.Get(EngineCodes.SqlServer); + } + + [Fact] + public void Join() + { + var query = new Query("Table").Join("TableA", "Column1", "ColumnA"); + var ctx = new SqlResult { Query = query }; + + Assert.Equal("\nINNER JOIN [TableA] ON [Column1] = [ColumnA]", compiler.CompileJoins(ctx)); + } + + + [Fact] + public void JoinWithIndexHint() + { + var query = new Query("Table").Join("TableA", "Column1", "ColumnA", indexHint: "index1"); + var ctx = new SqlResult { Query = query }; + + Assert.Equal("\nINNER JOIN [TableA] ON [Column1] = [ColumnA]", compiler.CompileJoins(ctx)); + } + } +} diff --git a/QueryBuilder.Tests/Sqlite/SqlliteJoinTests.cs b/QueryBuilder.Tests/Sqlite/SqlliteJoinTests.cs new file mode 100644 index 00000000..1bac4bf5 --- /dev/null +++ b/QueryBuilder.Tests/Sqlite/SqlliteJoinTests.cs @@ -0,0 +1,35 @@ +using SqlKata.Compilers; +using SqlKata.Tests.Infrastructure; +using Xunit; + +namespace SqlKata.Tests.Sqlite +{ + public class SqlliteJoinTests : TestSupport + { + private readonly SqliteCompiler compiler; + + public SqlliteJoinTests() + { + compiler = Compilers.Get(EngineCodes.Sqlite); + } + + [Fact] + public void Join() + { + var query = new Query("Table").Join("TableA", "Column1", "ColumnA"); + var ctx = new SqlResult { Query = query }; + + Assert.Equal("\nINNER JOIN \"TableA\" ON \"Column1\" = \"ColumnA\"", compiler.CompileJoins(ctx)); + } + + + [Fact] + public void JoinWithIndexHint() + { + var query = new Query("Table").Join("TableA", "Column1", "ColumnA", indexHint: "index1"); + var ctx = new SqlResult { Query = query }; + + Assert.Equal("\nINNER JOIN \"TableA\" ON \"Column1\" = \"ColumnA\"", compiler.CompileJoins(ctx)); + } + } +} diff --git a/QueryBuilder/BaseQuery.cs b/QueryBuilder/BaseQuery.cs index 86b44a23..9f4afaec 100644 --- a/QueryBuilder/BaseQuery.cs +++ b/QueryBuilder/BaseQuery.cs @@ -272,7 +272,16 @@ public Q From(string table) }); } - public Q From(Query query, string alias = null) + public Q From(string table, string indexHint) + { + return AddOrReplaceComponent("from", new FromClause + { + Table = table, + IndexHint = indexHint + }); + } + + public Q From(Query query, string alias = null, string indexHint = null) { query = query.Clone(); query.SetParent((Q)this); @@ -282,6 +291,11 @@ public Q From(Query query, string alias = null) query.As(alias); }; + if (indexHint != null) + { + query.UseIndexHint(alias); + }; + return AddOrReplaceComponent("from", new QueryFromClause { Query = query diff --git a/QueryBuilder/Clauses/FromClause.cs b/QueryBuilder/Clauses/FromClause.cs index 1410facf..c814ee62 100644 --- a/QueryBuilder/Clauses/FromClause.cs +++ b/QueryBuilder/Clauses/FromClause.cs @@ -6,12 +6,15 @@ namespace SqlKata public abstract class AbstractFrom : AbstractClause { protected string _alias; + protected string _indexHint { get; set; } /// /// Try to extract the Alias for the current clause. /// /// public virtual string Alias { get => _alias; set => _alias = value; } + + public string IndexHint { get => _indexHint; set => _indexHint = value; } } /// @@ -71,6 +74,7 @@ public override AbstractClause Clone() { Engine = Engine, Alias = Alias, + IndexHint = IndexHint, Query = Query.Clone(), Component = Component, }; @@ -116,4 +120,4 @@ public override AbstractClause Clone() }; } } -} \ No newline at end of file +} diff --git a/QueryBuilder/Clauses/JoinClause.cs b/QueryBuilder/Clauses/JoinClause.cs index 94c6b322..7309ada7 100644 --- a/QueryBuilder/Clauses/JoinClause.cs +++ b/QueryBuilder/Clauses/JoinClause.cs @@ -25,6 +25,7 @@ public override AbstractClause Clone() public class DeepJoin : AbstractJoin { public string Type { get; set; } + public string IndexHint { get; set; } public string Expression { get; set; } public string SourceKeySuffix { get; set; } public string TargetKey { get; set; } @@ -39,6 +40,7 @@ public override AbstractClause Clone() Engine = Engine, Component = Component, Type = Type, + IndexHint = IndexHint, Expression = Expression, SourceKeySuffix = SourceKeySuffix, TargetKey = TargetKey, diff --git a/QueryBuilder/Compilers/Compiler.cs b/QueryBuilder/Compilers/Compiler.cs index aa15c789..d7664765 100644 --- a/QueryBuilder/Compilers/Compiler.cs +++ b/QueryBuilder/Compilers/Compiler.cs @@ -897,7 +897,7 @@ public virtual string CompileFalse() return "false"; } - private InvalidCastException InvalidClauseException(string section, AbstractClause clause) + protected InvalidCastException InvalidClauseException(string section, AbstractClause clause) { return new InvalidCastException($"Invalid type \"{clause.GetType().Name}\" provided for the \"{section}\" clause."); } diff --git a/QueryBuilder/Compilers/MySqlCompiler.cs b/QueryBuilder/Compilers/MySqlCompiler.cs index 4729125b..80078672 100644 --- a/QueryBuilder/Compilers/MySqlCompiler.cs +++ b/QueryBuilder/Compilers/MySqlCompiler.cs @@ -1,3 +1,5 @@ +using System.Linq; + namespace SqlKata.Compilers { public class MySqlCompiler : Compiler @@ -10,6 +12,67 @@ public MySqlCompiler() public override string EngineCode { get; } = EngineCodes.MySql; + public override string CompileTableExpression(SqlResult ctx, AbstractFrom from) + { + if (from is RawFromClause raw) + { + ctx.Bindings.AddRange(raw.Bindings); + return WrapIdentifiers(raw.Expression); + } + + if (from is QueryFromClause queryFromClause) + { + var fromQuery = queryFromClause.Query; + + var alias = string.IsNullOrEmpty(fromQuery.QueryAlias) ? "" : $" {TableAsKeyword}" + WrapValue(fromQuery.QueryAlias); + + var subCtx = CompileSelectQuery(fromQuery); + + ctx.Bindings.AddRange(subCtx.Bindings); + + if (!string.IsNullOrWhiteSpace(fromQuery.IndexHint)) + { + subCtx.RawSql += $" USE INDEX({fromQuery.IndexHint})"; + } + + return "(" + subCtx.RawSql + ")" + alias; + } + + if (from is FromClause fromClause) + { + var fromStatment = Wrap(fromClause.Table); + + if (!string.IsNullOrWhiteSpace(fromClause.IndexHint)) + { + fromStatment += $" USE INDEX({fromClause.IndexHint})"; + } + + return fromStatment; + } + + throw InvalidClauseException("TableExpression", from); + } + + public override string CompileJoin(SqlResult ctx, Join join, bool isNested = false) + { + var from = join.GetOneComponent("from", EngineCode); + var conditions = join.GetComponents("where", EngineCode); + + var joinTable = CompileTableExpression(ctx, from); + var constraints = CompileConditions(ctx, conditions); + + var onClause = conditions.Any() ? $" ON {constraints}" : ""; + + var indexHint = ""; + + if (!string.IsNullOrWhiteSpace(join.IndexHint)) + { + indexHint = $" USE INDEX({join.IndexHint})"; + } + + return $"{join.Type} {joinTable}{indexHint}{onClause}"; + } + public override string CompileLimit(SqlResult ctx) { var limit = ctx.Query.GetLimit(EngineCode); diff --git a/QueryBuilder/Join.cs b/QueryBuilder/Join.cs index ae0969a6..9ec84ec3 100644 --- a/QueryBuilder/Join.cs +++ b/QueryBuilder/Join.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; namespace SqlKata { @@ -18,6 +19,8 @@ public string Type } } + public string IndexHint = null; + public Join() : base() { } @@ -73,5 +76,11 @@ public override Join NewQuery() { return new Join(); } + + public Join UsingIndexHint(string indexHint = null) + { + IndexHint = indexHint; + return this; + } } } diff --git a/QueryBuilder/Query.Join.cs b/QueryBuilder/Query.Join.cs index 3fe3bbb1..c8ac0fa5 100644 --- a/QueryBuilder/Query.Join.cs +++ b/QueryBuilder/Query.Join.cs @@ -20,50 +20,51 @@ public Query Join( string first, string second, string op = "=", - string type = "inner join" + string type = "inner join", + string indexHint = null ) { - return Join(j => j.JoinWith(table).WhereColumns(first, op, second).AsType(type)); + return Join(j => j.JoinWith(table).WhereColumns(first, op, second).AsType(type).UsingIndexHint(indexHint)); } - public Query Join(string table, Func callback, string type = "inner join") + public Query Join(string table, Func callback, string type = "inner join", string indexHint = null) { - return Join(j => j.JoinWith(table).Where(callback).AsType(type)); + return Join(j => j.JoinWith(table).Where(callback).AsType(type).UsingIndexHint(indexHint)); } - public Query Join(Query query, Func onCallback, string type = "inner join") + public Query Join(Query query, Func onCallback, string type = "inner join", string indexHint = null) { - return Join(j => j.JoinWith(query).Where(onCallback).AsType(type)); + return Join(j => j.JoinWith(query).Where(onCallback).AsType(type).UsingIndexHint(indexHint)); } - public Query LeftJoin(string table, string first, string second, string op = "=") + public Query LeftJoin(string table, string first, string second, string op = "=", string indexHint = null) { - return Join(table, first, second, op, "left join"); + return Join(table, first, second, op, "left join", indexHint); } - public Query LeftJoin(string table, Func callback) + public Query LeftJoin(string table, Func callback, string indexHint = null) { - return Join(table, callback, "left join"); + return Join(table, callback, "left join", indexHint); } - public Query LeftJoin(Query query, Func onCallback) + public Query LeftJoin(Query query, Func onCallback, string indexHint = null) { - return Join(query, onCallback, "left join"); + return Join(query, onCallback, "left join", indexHint); } - public Query RightJoin(string table, string first, string second, string op = "=") + public Query RightJoin(string table, string first, string second, string op = "=", string indexHint = null) { - return Join(table, first, second, op, "right join"); + return Join(table, first, second, op, "right join", indexHint); } - public Query RightJoin(string table, Func callback) + public Query RightJoin(string table, Func callback, string indexHint = null) { - return Join(table, callback, "right join"); + return Join(table, callback, "right join", indexHint); } - public Query RightJoin(Query query, Func onCallback) + public Query RightJoin(Query query, Func onCallback, string indexHint = null) { - return Join(query, onCallback, "right join"); + return Join(query, onCallback, "right join", indexHint); } public Query CrossJoin(string table) diff --git a/QueryBuilder/Query.cs b/QueryBuilder/Query.cs index 8435eca6..c8b1529f 100755 --- a/QueryBuilder/Query.cs +++ b/QueryBuilder/Query.cs @@ -12,6 +12,7 @@ public partial class Query : BaseQuery public bool IsDistinct { get; set; } = false; public string QueryAlias { get; set; } + public string IndexHint { get; set; } public string Method { get; set; } = "select"; public List Includes = new List(); public Dictionary Variables = new Dictionary(); @@ -20,9 +21,9 @@ public Query() : base() { } - public Query(string table, string comment = null) : base() + public Query(string table, string comment = null, string indexHint = null) : base() { - From(table); + From(table, indexHint); Comment(comment); } @@ -53,6 +54,7 @@ public override Query Clone() var clone = base.Clone(); clone.Parent = Parent; clone.QueryAlias = QueryAlias; + clone.IndexHint = IndexHint; clone.IsDistinct = IsDistinct; clone.Method = Method; clone.Includes = Includes; @@ -66,6 +68,12 @@ public Query As(string alias) return this; } + public Query UseIndexHint(string indexHint) + { + IndexHint = indexHint; + return this; + } + /// /// Sets a comment for the query. ///