From 1ad507e76c8f2f52c4b2f2b69e5426e769e2b3fe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 10 Apr 2026 08:50:06 +0000 Subject: [PATCH 1/2] Initial plan From 385d3d538b829f272a6920470ef67891457f38bf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 10 Apr 2026 08:58:33 +0000 Subject: [PATCH 2/2] Fix Query.Clone() shallow copy of Includes and Variables - Add Clone() method to Include class for deep copying - Fix Query.Clone() to deep-copy Includes list and Variables dictionary - Fix XQuery.Clone() with same deep-copy fixes - Add unit tests for the clone bug (independent list, objects, dict, properties) Fixes #747 Agent-Logs-Url: https://github.com/sqlkata/querybuilder/sessions/d82ad628-73f5-4c2f-b56e-f4021ce1519f Co-authored-by: ahmad-moussawi <2517523+ahmad-moussawi@users.noreply.github.com> --- QueryBuilder.Tests/GeneralTests.cs | 65 ++++++++++++++++++++++++++++++ QueryBuilder/Include.cs | 12 ++++++ QueryBuilder/Query.cs | 4 +- SqlKata.Execution/XQuery.cs | 5 ++- 4 files changed, 82 insertions(+), 4 deletions(-) diff --git a/QueryBuilder.Tests/GeneralTests.cs b/QueryBuilder.Tests/GeneralTests.cs index 63fb3d3f..ae20a469 100644 --- a/QueryBuilder.Tests/GeneralTests.cs +++ b/QueryBuilder.Tests/GeneralTests.cs @@ -601,5 +601,70 @@ public void Passing_Negative_Boolean_False_To_Where_Should_Call_WhereTrue_Or_Whe Assert.Equal("SELECT * FROM [Table] WHERE [Col] != cast(0 as bit)", c[EngineCodes.SqlServer].ToString()); } + + [Fact] + public void Clone_ShouldProduceIndependentIncludesList() + { + var query = new Query("users") + .Include("posts", new Query("posts"), "user_id", "id"); + + var clone = query.Clone(); + + // Adding an include to the clone should not affect the original + clone.Include("comments", new Query("comments"), "user_id", "id"); + + Assert.Single(query.Includes); + Assert.Equal(2, clone.Includes.Count); + } + + [Fact] + public void Clone_ShouldProduceIndependentIncludeObjects() + { + var query = new Query("users") + .Include("posts", new Query("posts"), "user_id", "id"); + + var clone = query.Clone(); + + // Modifying an include property on the clone should not affect the original + clone.Includes[0].Name = "modified_name"; + + Assert.Equal("posts", query.Includes[0].Name); + Assert.Equal("modified_name", clone.Includes[0].Name); + } + + [Fact] + public void Clone_ShouldProduceIndependentVariablesDictionary() + { + var query = new Query("users").Define("limit", 10); + + var clone = query.Clone(); + + // Adding a variable to the clone should not affect the original + clone.Define("offset", 5); + + Assert.False(query.Variables.ContainsKey("offset")); + Assert.True(clone.Variables.ContainsKey("offset")); + } + + [Fact] + public void Clone_ShouldPreserveAllProperties() + { + var query = new Query("users") + .Select("id", "name") + .Where("active", true) + .Distinct() + .As("u") + .Include("posts", new Query("posts"), "user_id", "id") + .Define("myvar", 42); + + var clone = query.Clone(); + + Assert.Equal(query.QueryAlias, clone.QueryAlias); + Assert.Equal(query.IsDistinct, clone.IsDistinct); + Assert.Equal(query.Method, clone.Method); + Assert.Equal(query.Includes.Count, clone.Includes.Count); + Assert.Equal(query.Includes[0].Name, clone.Includes[0].Name); + Assert.Equal(query.Variables["myvar"], clone.Variables["myvar"]); + } } } diff --git a/QueryBuilder/Include.cs b/QueryBuilder/Include.cs index e0cbfe02..aefec7f4 100644 --- a/QueryBuilder/Include.cs +++ b/QueryBuilder/Include.cs @@ -7,5 +7,17 @@ public class Include public string ForeignKey { get; set; } public string LocalKey { get; set; } public bool IsMany { get; set; } + + public Include Clone() + { + return new Include + { + Name = Name, + Query = Query.Clone(), + ForeignKey = ForeignKey, + LocalKey = LocalKey, + IsMany = IsMany, + }; + } } } diff --git a/QueryBuilder/Query.cs b/QueryBuilder/Query.cs index 8435eca6..8f5bd22f 100755 --- a/QueryBuilder/Query.cs +++ b/QueryBuilder/Query.cs @@ -55,8 +55,8 @@ public override Query Clone() clone.QueryAlias = QueryAlias; clone.IsDistinct = IsDistinct; clone.Method = Method; - clone.Includes = Includes; - clone.Variables = Variables; + clone.Includes = Includes.Select(i => i.Clone()).ToList(); + clone.Variables = new Dictionary(Variables); return clone; } diff --git a/SqlKata.Execution/XQuery.cs b/SqlKata.Execution/XQuery.cs index 3cb5f14f..ef52cafe 100644 --- a/SqlKata.Execution/XQuery.cs +++ b/SqlKata.Execution/XQuery.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Data; using System.Linq; using SqlKata.Compilers; @@ -35,8 +36,8 @@ public override Query Clone() query.QueryAlias = QueryAlias; query.IsDistinct = IsDistinct; query.Method = Method; - query.Includes = Includes; - query.Variables = Variables; + query.Includes = Includes.Select(i => i.Clone()).ToList(); + query.Variables = new Dictionary(Variables); query.SetEngineScope(EngineScope);