diff --git a/demo/TypeSafeBuild/TypeSafeBuild.csproj b/demo/AOPFiltering/Demo.AOPFiltering.csproj
similarity index 100%
rename from demo/TypeSafeBuild/TypeSafeBuild.csproj
rename to demo/AOPFiltering/Demo.AOPFiltering.csproj
diff --git a/demo/AOPFiltering/Program.cs b/demo/AOPFiltering/Program.cs
new file mode 100644
index 00000000..3c120054
--- /dev/null
+++ b/demo/AOPFiltering/Program.cs
@@ -0,0 +1,209 @@
+using Carbunql;
+using Carbunql.TypeSafe;
+using Carbunql.Building;
+
+internal class Program
+{
+ private static void Main(string[] args)
+ {
+ var staffId = 1;
+
+ //全件取得
+ Console.WriteLine(SelectAreaSaleReport().ToText());
+ Console.WriteLine(";");
+
+ Console.WriteLine(SelectAreaSaleReportByStaffId(staffId).ToText());
+ Console.WriteLine(";");
+
+ //帳票別フィルタリング
+ Console.WriteLine(SelectStoreSaleReport().ToText());
+ Console.WriteLine(";");
+
+ Console.WriteLine(SelectStoreSaleReportByStaffId(staffId).ToText());
+ Console.WriteLine(";");
+
+ //アスペクト指向フィルタリング
+ Console.WriteLine(SelectAreaSaleReport().FilterByStaffId(staffId).ToText());
+ Console.WriteLine(";");
+
+ Console.WriteLine(SelectStoreSaleReport().FilterByStaffId(staffId).ToText());
+ Console.WriteLine(";");
+ }
+
+ ///
+ /// Area_sale_reportを全件取得します
+ ///
+ ///
+ private static FluentSelectQuery SelectAreaSaleReport()
+ {
+ var r = Sql.DefineDataSet();
+ return Sql.From(() => r).Select(() => r).Compile();
+ }
+
+ ///
+ /// Area_sale_reportをstaff_idでフィルタリングします
+ ///
+ ///
+ ///
+ private static FluentSelectQuery SelectAreaSaleReportByStaffId(long staffId)
+ {
+ var r = Sql.DefineDataSet(() => SelectAreaSaleReport());
+ return Sql.From(() => r)
+ .Where(() => r.sales_staff_id == staffId);
+ }
+
+ ///
+ /// Store_sale_reportを全件取得します
+ ///
+ ///
+ private static FluentSelectQuery SelectStoreSaleReport()
+ {
+ var r = Sql.DefineDataSet();
+ return Sql.From(() => r).Select(() => r).Compile();
+ }
+
+ ///
+ /// スタッフとストアの関連を示すテーブルを取得します
+ /// (おおげさ)
+ ///
+ ///
+ private static FluentSelectQuery SelectAreaDetailWithStaff()
+ {
+ var a = Sql.DefineDataSet();
+ var d = Sql.DefineDataSet();
+ return Sql.From(() => d)
+ .InnerJoin(() => a, () => d.area_id == a.area_id)
+ .Select(() => d)
+ .Select(() => new
+ {
+ a.sales_staff_id
+ }).Compile();
+ }
+
+ ///
+ /// Store_sale_reportをstaff_idでフィルタリングします
+ ///
+ ///
+ ///
+ private static FluentSelectQuery SelectStoreSaleReportByStaffId(long staffId)
+ {
+ var r = Sql.DefineDataSet(() => SelectStoreSaleReport());
+ return Sql.From(() => r)
+ .Exists(SelectAreaDetailWithStaff, x => r.store_id == x.store_id && x.sales_staff_id == staffId);
+ }
+}
+
+public static class AspectOrientedFiltering
+{
+ ///
+ /// 選択クエリをスタッフIDでフィルタリングします。
+ /// フィルタリングできない場合は例外が発生します
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static FluentSelectQuery FilterByStaffId(this FluentSelectQuery query, long staffId) where T : IDataRow, new()
+ {
+ // 列「sales_staff_id」が存在する場合、該当列でフィルタする
+ var sales_staff_id = query.SelectClause!.Where(x => x.Alias == "sales_staff_id").FirstOrDefault()?.Alias;
+ if (sales_staff_id != null)
+ {
+ //タイプセーフではないビルド
+ //引数のクエリをサブクエリqとして定義する
+ var sq = new SelectQuery();
+ var (f, q) = sq.From(query).As("q");
+ sq.SelectAll(q);
+
+ //列「sales_staff_id」を検索条件にする
+ sq.Where(q, sales_staff_id).Equal(sq.AddParameter(":staff_id", staffId));
+
+ //コメントを足す
+ sq.AddComment(nameof(FilterByStaffId));
+
+ //タイプセーフに戻す
+ return sq.Compile();
+ }
+
+ // 列「store_id」が存在する場合、areaテーブル経由でフィルタする
+ var store_id = query.SelectClause!.Where(x => x.Alias == "store_id").FirstOrDefault()?.Alias;
+ if (store_id != null)
+ {
+ //タイプセーフではないビルド
+ //引数のクエリをサブクエリqとして定義する
+ var sq = new SelectQuery();
+ var (_, q) = sq.From(query).As("q");
+ sq.SelectAll(q);
+
+ //列「store_id」を検索条件にする
+ sq.Where(() =>
+ {
+ // area をstaff_id でフィルタし、
+ // area_detail と結合して、store_id に展開する
+ var xsq = new SelectQuery();
+ var (f, d) = xsq.From("area_detail").As("d");
+ var a = f.InnerJoin("area").As("a").On(d, "area_id");
+ xsq.Where(a, "sales_staff_id").Equal(xsq.AddParameter(":staff_id", staffId));
+ xsq.Where(q, store_id).Equal(d, store_id);
+ return xsq.ToExists();
+ });
+
+ //コメントを足す
+ sq.AddComment($"{nameof(FilterByStaffId)}, column:store_id");
+
+ //タイプセーフに戻す
+ return sq.Compile();
+ }
+
+ throw new NotImplementedException();
+ }
+}
+
+public record Area : IDataRow
+{
+ public long area_id { get; set; }
+
+ public long area_name { get; set; }
+
+ public long sales_staff_id { get; set; }
+
+ public IDataSet DataSet { get; set; } = null!;
+}
+
+public record Area_detail : IDataRow
+{
+ public long area_id { get; set; }
+
+ public long store_id { get; set; }
+
+ public IDataSet DataSet { get; set; } = null!;
+}
+
+public record Area_detail_with_staff : IDataRow
+{
+ public long area_id { get; set; }
+
+ public long store_id { get; set; }
+
+ public long sales_staff_id { get; set; }
+
+ public IDataSet DataSet { get; set; } = null!;
+}
+
+public record Area_sale_report : IDataRow
+{
+ public long area_id { get; set; }
+ public long sales_staff_id { get; set; }
+ public decimal sale_price { get; set; }
+
+ public IDataSet DataSet { get; set; } = null!;
+}
+
+public record Store_sale_report : IDataRow
+{
+ public long store_id { get; set; }
+ public decimal sale_price { get; set; }
+
+ public IDataSet DataSet { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/demo/TypeSafeBuild/Demo.TypeSafeBuild.csproj b/demo/TypeSafeBuild/Demo.TypeSafeBuild.csproj
new file mode 100644
index 00000000..1f9ab766
--- /dev/null
+++ b/demo/TypeSafeBuild/Demo.TypeSafeBuild.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/src/Carbunql.TypeSafe/SelectQueryExtension.cs b/src/Carbunql.TypeSafe/SelectQueryExtension.cs
new file mode 100644
index 00000000..83402384
--- /dev/null
+++ b/src/Carbunql.TypeSafe/SelectQueryExtension.cs
@@ -0,0 +1,110 @@
+using Carbunql.Annotations;
+using Carbunql.Building;
+using Carbunql.Definitions;
+using Carbunql.Tables;
+using System.Data;
+
+namespace Carbunql.TypeSafe;
+
+public static class SelectQueryExtension
+{
+
+ public static FluentSelectQuery Compile(this SelectQuery source, bool force = false) where T : IDataRow, new()
+ {
+ var q = new FluentSelectQuery();
+
+ // Copy clauses and parameters to the new query object
+ q.WithClause = source.WithClause;
+ q.SelectClause = source.SelectClause;
+ q.FromClause = source.FromClause;
+ q.WhereClause = source.WhereClause;
+ q.GroupClause = source.GroupClause;
+ q.HavingClause = source.HavingClause;
+ q.WindowClause = source.WindowClause;
+ q.OperatableQueries = source.OperatableQueries;
+ q.OrderClause = source.OrderClause;
+ q.LimitClause = source.LimitClause;
+ q.Parameters = source.Parameters;
+ q.CommentClause = source.CommentClause;
+ q.HeaderCommentClause = source.HeaderCommentClause;
+
+ var columns = PropertySelector.SelectLiteralProperties().Select(x => x.Name);
+
+ if (force)
+ {
+ foreach (var item in columns)
+ {
+ q.Select(q.FromClause!.Root, item);
+ }
+ }
+
+ TypeValidate(q, columns);
+
+ if (q.SelectClause == null)
+ {
+ foreach (var item in columns)
+ {
+ q.Select(q.FromClause!.Root, item);
+ }
+ };
+
+ return q;
+ }
+
+ private static void TypeValidate(SelectQuery q)
+ {
+ TypeValidate(q, PropertySelector.SelectLiteralProperties().Select(x => x.Name));
+ }
+
+ private static void TypeValidate(SelectQuery q, IEnumerable columns)
+ {
+ if (q.SelectClause != null && !(q.SelectClause.Count == 1 && q.SelectClause[0].Alias == "*"))
+ {
+ // Check if all properties of T are specified in the select clause
+ var aliases = q.GetSelectableItems().Select(x => x.Alias).ToHashSet();
+ var missingColumns = columns.Where(item => !aliases.Contains(item)).ToList();
+
+ if (missingColumns.Any())
+ {
+ // If there are missing columns, include all of them in the error message
+ throw new InvalidProgramException($"The select query is not compatible with '{typeof(T).Name}'. The following columns are missing: {string.Join(", ", missingColumns)}");
+ }
+ return;
+ }
+ else if (q.FromClause != null)
+ {
+ if (q.FromClause.Root.Table is VirtualTable v && v.Query is SelectQuery vq)
+ {
+ TypeValidate(vq, columns);
+ return;
+ }
+ else if (q.FromClause.Root.Table is CTETable ct)
+ {
+ // Check if all properties of T are specified in the select clause
+ var aliases = ct.GetColumnNames().ToHashSet();
+ var missingColumns = columns.Where(item => !aliases.Contains(item)).ToList();
+
+ if (missingColumns.Any())
+ {
+ // If there are missing columns, include all of them in the error message
+ throw new InvalidProgramException($"The select query is not compatible with '{typeof(T).Name}'. The following columns are missing: {string.Join(", ", missingColumns)}");
+ }
+ return;
+ }
+
+ var actual = q.FromClause.Root.Table.GetTableFullName();
+ var clause = TableDefinitionClauseFactory.Create();
+ var expect = clause.GetTableFullName();
+
+ if (!actual.Equals(expect))
+ {
+ throw new InvalidProgramException($"The select query is not compatible with '{typeof(T).Name}'. Expect: {expect}, Actual: {actual}");
+ }
+ return;
+ }
+ else
+ {
+ throw new InvalidProgramException($"The select query is not compatible with '{typeof(T).Name}'. FromClause is null.");
+ }
+ }
+}
diff --git a/src/Carbunql.sln b/src/Carbunql.sln
index 864eacde..637a21d0 100644
--- a/src/Carbunql.sln
+++ b/src/Carbunql.sln
@@ -33,7 +33,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Carbunql.TypeSafe", "Carbun
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Carbunql.TypeSafe.Test", "..\test\Carbunql.TypeSafe.Test\Carbunql.TypeSafe.Test.csproj", "{EA3034E3-564E-46B4-9FA8-B0A10D9DCBCE}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TypeSafeBuild", "..\demo\TypeSafeBuild\TypeSafeBuild.csproj", "{680F4563-4F6A-454C-B769-5FF7F768B7FF}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo.AOPFiltering", "..\demo\AOPFiltering\Demo.AOPFiltering.csproj", "{63CE8E1D-71A9-4ACE-AE52-613B99394060}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo.TypeSafeBuild", "..\demo\TypeSafeBuild\Demo.TypeSafeBuild.csproj", "{B1016F33-6565-4A6E-844E-EF966E54F862}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -101,10 +103,14 @@ Global
{EA3034E3-564E-46B4-9FA8-B0A10D9DCBCE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EA3034E3-564E-46B4-9FA8-B0A10D9DCBCE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EA3034E3-564E-46B4-9FA8-B0A10D9DCBCE}.Release|Any CPU.Build.0 = Release|Any CPU
- {680F4563-4F6A-454C-B769-5FF7F768B7FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {680F4563-4F6A-454C-B769-5FF7F768B7FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {680F4563-4F6A-454C-B769-5FF7F768B7FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {680F4563-4F6A-454C-B769-5FF7F768B7FF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {63CE8E1D-71A9-4ACE-AE52-613B99394060}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {63CE8E1D-71A9-4ACE-AE52-613B99394060}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {63CE8E1D-71A9-4ACE-AE52-613B99394060}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {63CE8E1D-71A9-4ACE-AE52-613B99394060}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B1016F33-6565-4A6E-844E-EF966E54F862}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B1016F33-6565-4A6E-844E-EF966E54F862}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B1016F33-6565-4A6E-844E-EF966E54F862}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B1016F33-6565-4A6E-844E-EF966E54F862}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/Carbunql/Clauses/ValueBase.cs b/src/Carbunql/Clauses/ValueBase.cs
index 196d7d53..7996b058 100644
--- a/src/Carbunql/Clauses/ValueBase.cs
+++ b/src/Carbunql/Clauses/ValueBase.cs
@@ -25,6 +25,8 @@ namespace Carbunql.Clauses;
[Union(12, typeof(QueryContainer))]
[Union(13, typeof(ValueCollection))]
[Union(14, typeof(Interval))]
+[Union(15, typeof(LikeClause))]
+[Union(16, typeof(ExistsExpression))]
public abstract class ValueBase : IQueryCommandable
{
///