csharp/0x1000000/SqExpress/SqExpress.CodegenUtil/CodeGen/ModelClassGenerator.cs

ModelClassGenerator.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Codeastysis;
using Microsoft.Codeastysis.CSharp;
using Microsoft.Codeastysis.CSharp.Syntax;
using SqExpress.CodeGenUtil.Model.SqModel;
using SqExpress.QueryBuilders.RecordSetter;
using SqExpress.Syntax.Names;
using static SqExpress.CodeGenUtil.CodeGen.SyntaxHelpers;

namespace SqExpress.CodeGenUtil.CodeGen
{
    internal clast ModelClastGenerator
    {
        private const string MethodNameGetColumns = "GetColumns";
        private const string MethodNameGetMapping = "GetMapping";
        private const string MethodNameGetUpdateKeyMapping = "GetUpdateKeyMapping";
        private const string MethodNameGetUpdateMapping = "GetUpdateMapping";
        private const string MethodNameRead = "Read";
        private const string MethodNameReadOrdinal = "ReadOrdinal";
        private const string ReaderClastSuffix = "Reader";
        private const string MethodNameGetReader = "GetReader";
        private const string UpdaterClastSuffix = "Updater";
        private const string MethodNameGetUpdater = "GetUpdater";

        private static readonly HashSet AllMethods = new HashSet
        {
            MethodNameGetColumns,
            MethodNameGetMapping,
            MethodNameGetUpdateKeyMapping,
            MethodNameGetUpdateMapping,
            MethodNameRead,
            MethodNameReadOrdinal,
            MethodNameGetReader,
            MethodNameGetUpdater
        };

        public static CompilationUnitSyntax Generate(SqModelMeta meta, string defaultNamespace, string existingFilePath, bool rwClastes, IFileSystem fileSystem, out bool existing)
        {
            CompilationUnitSyntax result;
            ClastDeclarationSyntax? existingClast = null;

            existing = false;

            if (fileSystem.FileExists(existingFilePath))
            {
                existing = true;
                var tClast = CSharpSyntaxTree.ParseText(fileSystem.ReadAllText(existingFilePath));

                existingClast = tClast.GetRoot()
                    .DescendantNodes()
                    .OfType()
                    .FirstOrDefault(cd => cd.Identifier.ValueText == meta.Name);
            }

            var namespaces =
                new[] {
                        nameof(System),
                        nameof(SqExpress),
                        $"{nameof(SqExpress)}.{nameof(SqExpress.QueryBuilders)}.{nameof(SqExpress.QueryBuilders.RecordSetter)}"
                    }
                    .Concat(meta.Properties.SelectMany(p => p.Column)
                        .Select(c => c.TableRef.TableTypeNameSpace)
                        .Where(n => n != defaultNamespace))
                    .Distinct()
                    .ToList();

            if (rwClastes || ExtractTableRefs(meta).Any(tr => tr.BaseTypeKindTag == BaseTypeKindTag.DerivedTableBase))
            {
                namespaces.Add($"{nameof(SqExpress)}.{nameof(SqExpress.Syntax)}.{nameof(SqExpress.Syntax.Names)}");
                namespaces.Add($"{nameof(System)}.{nameof(System.Collections)}.{nameof(System.Collections.Generic)}");
            }


            if (existingClast != null)
            {
                result = existingClast.FindParentOrDefault() ?? throw new SqExpressCodeGenException($"Could not find compilation unit in \"{existingFilePath}\"");

                foreach (var usingDirectiveSyntax in result.Usings)
                {
                    var existingUsing = usingDirectiveSyntax.Name.ToFullString();
                    var index = namespaces.IndexOf(existingUsing);
                    if (index >= 0)
                    {
                        namespaces.RemoveAt(index);
                    }
                }

                if (namespaces.Count > 0)
                {
                    result = result.AddUsings(namespaces
                        .Select(n => SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(n)))
                        .ToArray());
                }
                result = result.ReplaceNode(existingClast, GenerateClast(meta, rwClastes, existingClast));
            }
            else
            {
                result = SyntaxFactory.CompilationUnit()
                    .AddUsings(namespaces.Select(n => SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(n))).ToArray())
                    .AddMembers(SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName(defaultNamespace))
                        .AddMembers(GenerateClast(meta, rwClastes, null)));
            }

            return result.NormalizeWhitespace();
        }

        private static ClastDeclarationSyntax GenerateClast(SqModelMeta meta, bool rwClastes, ClastDeclarationSyntax? existingClast)
        {
            ClastDeclarationSyntax result;
            MemberDeclarationSyntax[]? oldMembers = null;
            Dictionary? oldAttributes = null;
            if (existingClast != null)
            {
                result = existingClast;

                oldMembers = result.Members
                    .Where(md =>
                    {
                        if (md is ConstructorDeclarationSyntax)
                        {
                            return false;
                        }

                        if (md is IncompleteMemberSyntax)
                        {
                            return false;
                        }

                        if (md is PropertyDeclarationSyntax p)
                        {
                            if (meta.Properties.Any(mp => mp.Name == p.Identifier.ValueText))
                            {
                                if (p.AttributeLists.Count > 0)
                                {
                                    oldAttributes ??= new Dictionary();
                                    oldAttributes.Add(p.Identifier.ValueText, p.AttributeLists);
                                }
                                return false;
                            }
                        }

                        if (md is MethodDeclarationSyntax method)
                        {
                            var name = method.Identifier.ValueText;

                            if (name.StartsWith("With") || AllMethods.Contains(name) || name.StartsWith(MethodNameGetReader + "For") || name.StartsWith(MethodNameGetUpdater + "For"))
                            {
                                return false;
                            }
                        }

                        if (md is ClastDeclarationSyntax clastDeclaration)
                        {
                            var name = clastDeclaration.Identifier.ValueText;

                            if (name == meta.Name + ReaderClastSuffix || name.StartsWith(meta.Name + ReaderClastSuffix + "For"))
                            {
                                return false;
                            }
                            if (name == meta.Name + UpdaterClastSuffix || name.StartsWith(meta.Name + UpdaterClastSuffix + "For"))
                            {
                                return false;
                            }
                        }

                        return true;
                    })
                    .ToArray();

                result = result.RemoveNodes(result.DescendantNodes().OfType(), SyntaxRemoveOptions.KeepNoTrivia)!;

            }
            else
            {
                result = SyntaxFactory.ClastDeclaration(meta.Name)
                    .WithModifiers(existingClast?.Modifiers ?? Modifiers(SyntaxKind.PublicKeyword));
            }

            result = result
                .AddMembers(Constructors(meta)
                    .Concat(GenerateStaticFactory(meta))
                    .Concat(rwClastes ? GenerateOrdinalStaticFactory(meta) : Array.Empty())
                    .Concat(Properties(meta, oldAttributes))
                    .Concat(GenerateWithModifiers(meta))
                    .Concat(GenerateGetColumns(meta))
                    .Concat(GenerateMapping(meta))
                    .Concat(rwClastes ? GenerateReaderClast(meta): Array.Empty())
                    .Concat(rwClastes ? GenerateWriterClast(meta) : Array.Empty())
                    .ToArray());

            if (oldMembers != null && oldMembers.Length > 0)
            {
                result = result.AddMembers(oldMembers);
            }

            return result;
        }

        public static IEnumerable Properties(SqModelMeta meta, IReadOnlyDictionary? oldAttributes)
        {
            return meta.Properties.Select(p =>
            {
                var res = SyntaxFactory.PropertyDeclaration(
                        SyntaxFactory.ParseTypeName(p.FinalType),
                        p.Name)
                    .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
                    .AddAccessorListAccessors(
                        SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
                            .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))
                    );
                if (oldAttributes != null && oldAttributes.TryGetValue(p.Name, out var attributeList))
                {
                    res = res.WithAttributeLists(attributeList);
                }

                return res;
            });
        }

        public static MemberDeclarationSyntax[] Constructors(SqModelMeta meta)
        {
            var constructor = SyntaxFactory.ConstructorDeclaration(meta.Name)
                .WithModifiers(Modifiers(SyntaxKind.PublicKeyword))
                .AddParameterListParameters(meta.Properties.Select(p=> FuncParameter(p.Name.FirstToLower(), p.FinalType)).ToArray())
                .WithBody(SyntaxFactory.Block(GenerateConstructorastignments(meta)));

            return new MemberDeclarationSyntax[] {constructor};
        }

        private static IEnumerable GenerateConstructorastignments(SqModelMeta meta)
        {
            return meta.Properties.Select(p =>
                SyntaxFactory.ExpressionStatement(astignmentThis(p.Name,
                    SyntaxFactory.IdentifierName(p.Name.FirstToLower()))));
        }

        public static IEnumerable GenerateStaticFactory(SqModelMeta meta)
        {
            return ExtractTableRefs(meta).Select(tableRef => SyntaxFactory
                    .MethodDeclaration(SyntaxFactory.ParseTypeName(meta.Name), MethodNameRead)
                    .WithModifiers(Modifiers(SyntaxKind.PublicKeyword, SyntaxKind.StaticKeyword))
                    .AddParameterListParameters(FuncParameter("record", nameof(ISqDataRecordReader)))
                    .AddParameterListParameters(FuncParameter("table", ExtractTableTypeName(meta, tableRef)))
                    .WithBody(SyntaxFactory.Block(SyntaxFactory.ReturnStatement(
                        SyntaxFactory.ObjectCreationExpression(SyntaxFactory.Token(SyntaxKind.NewKeyword),
                            SyntaxFactory.ParseTypeName(meta.Name),
                            ArgumentList(meta.Properties.Select(p =>
                                {
                                    ExpressionSyntax invocation = MemberAccess("table", p.Column.First().ColumnName)
                                        .MemberAccess("Read")
                                        .Invoke(SyntaxFactory.ParseName("record"));

                                    if (p.CastType != null)
                                    {
                                        invocation = SyntaxFactory.CastExpression(SyntaxFactory.ParseTypeName(p.CastType), invocation);
                                    }
                                    return new NamedArgument(p.Name.FirstToLower(),
                                        invocation);
                                })
                                .ToArray()),
                            null)))));
        }

        public static IEnumerable GenerateOrdinalStaticFactory(SqModelMeta meta)
        {
            return ExtractTableRefs(meta).Select(tableRef => SyntaxFactory
                    .MethodDeclaration(SyntaxFactory.ParseTypeName(meta.Name), MethodNameReadOrdinal)
                    .WithModifiers(Modifiers(SyntaxKind.PublicKeyword, SyntaxKind.StaticKeyword))
                    .AddParameterListParameters(FuncParameter("record", nameof(ISqDataRecordReader)))
                    .AddParameterListParameters(FuncParameter("table", ExtractTableTypeName(meta, tableRef)))
                    .AddParameterListParameters(FuncParameter("offset", "int"))
                    .WithBody(SyntaxFactory.Block(SyntaxFactory.ReturnStatement(
                        SyntaxFactory.ObjectCreationExpression(SyntaxFactory.Token(SyntaxKind.NewKeyword),
                            SyntaxFactory.ParseTypeName(meta.Name),
                            ArgumentList(meta.Properties.Select((p,index) =>
                                {
                                    ExpressionSyntax invocation = MemberAccess("table", p.Column.First().ColumnName)
                                        .MemberAccess("Read")
                                        .Invoke(
                                            SyntaxFactory.ParseName("record"),
                                            index == 0 
                                                ? SyntaxFactory.IdentifierName("offset")
                                                : SyntaxFactory.BinaryExpression(SyntaxKind.AddExpression, SyntaxFactory.IdentifierName("offset"), SyntaxFactory.LiteralExpression(SyntaxKind.NumerireplacederalExpression, SyntaxFactory.Literal(index))));

                                    if (p.CastType != null)
                                    {
                                        invocation = SyntaxFactory.CastExpression(SyntaxFactory.ParseTypeName(p.CastType), invocation);
                                    }
                                    return new NamedArgument(p.Name.FirstToLower(),
                                        invocation);
                                })
                                .ToArray()),
                            null)))));
        }

        public static IEnumerable GenerateGetColumns(SqModelMeta meta)
        {
            return ExtractTableRefs(meta).Select(tableRef =>
                {
                    string columnTypeName = ExtractTableColumnTypeName(tableRef);
                    var arrayItems = meta.Properties.Select(p => p.Column.First())
                        .Select(p => SyntaxFactory.IdentifierName("table").MemberAccess(p.ColumnName));
                    var arrayType = SyntaxFactory.ArrayType(
                        SyntaxFactory.IdentifierName(columnTypeName),
                        new SyntaxList(new[]
                        {
                            SyntaxFactory.ArrayRankSpecifier(SyntaxFactory.Token(SyntaxKind.OpenBracketToken),
                                new SeparatedSyntaxList(),
                                SyntaxFactory.Token(SyntaxKind.CloseBracketToken))
                        }));
                    var array = SyntaxFactory.ArrayCreationExpression(
                        arrayType,
                        SyntaxFactory.InitializerExpression(SyntaxKind.ArrayInitializerExpression,
                            new SeparatedSyntaxList().AddRange(arrayItems))
                    );

                    return SyntaxFactory
                        .MethodDeclaration(arrayType, MethodNameGetColumns)
                        .WithModifiers(Modifiers(SyntaxKind.PublicKeyword, SyntaxKind.StaticKeyword))
                        .AddParameterListParameters(FuncParameter("table", ExtractTableTypeName(meta, tableRef)))
                        .WithBody(SyntaxFactory.Block(SyntaxFactory.ReturnStatement(
                            array
                        )));

                });
        }

        public static IEnumerable GenerateMapping(SqModelMeta meta)
        {
            return ExtractTableRefs(meta).SelectMany(tr => GenerateMapping(meta, tr));
        }

        public static MemberDeclarationSyntax[] GenerateMapping(SqModelMeta meta, SqModelTableRef tableRef)
        {
            if (!HasUpdater(tableRef))
            {
                return Array.Empty();
            }

            if (meta.HasPk())
            {
                return new []
                {
                    MethodDeclarationSyntax(meta, tableRef, MethodNameGetMapping, null),
                    MethodDeclarationSyntax(meta, tableRef,MethodNameGetUpdateKeyMapping, true),
                    MethodDeclarationSyntax(meta, tableRef,MethodNameGetUpdateMapping, false)
                };
            }
            else
            {
                return new [] { MethodDeclarationSyntax(meta, tableRef, MethodNameGetMapping, null) };
            }


            static MemberDeclarationSyntax MethodDeclarationSyntax(SqModelMeta sqModelMeta, SqModelTableRef tableRef, string name, bool? pkFilter)
            {
                var setter = SyntaxFactory.IdentifierName("s");
                ExpressionSyntax chain = setter;

                foreach (var metaProperty in sqModelMeta.Properties.Where(p => pkFilter.HasValue? p.IsPrimaryKey == pkFilter.Value : !p.IsIdensaty))
                {
                    var col = setter.MemberAccess(nameof(IDataMapSetter.Target))
                        .MemberAccess(metaProperty.Column.First(c=>c.TableRef.Equals(tableRef)).ColumnName);
                    ExpressionSyntax prop = setter.MemberAccess(nameof(IDataMapSetter.Source))
                        .MemberAccess(metaProperty.Name);
                    if (metaProperty.CastType != null)
                    {
                        prop = SyntaxFactory.CastExpression(SyntaxFactory.ParseTypeName(metaProperty.Type), prop);
                    }

                    chain = chain.MemberAccess("Set").Invoke(col, prop);
                }


                var methodDeclarationSyntax = SyntaxFactory
                    .MethodDeclaration(SyntaxFactory.ParseTypeName(nameof(IRecordSetterNext)), name)
                    .WithModifiers(Modifiers(SyntaxKind.PublicKeyword, SyntaxKind.StaticKeyword))
                    .AddParameterListParameters(FuncParameter("s",
                        $"{nameof(IDataMapSetter)}"))
                    .WithBody(SyntaxFactory.Block(SyntaxFactory.ReturnStatement(chain)));
                return methodDeclarationSyntax;
            }
        }

        public static IEnumerable GenerateWithModifiers(SqModelMeta meta)
        {
            return meta.Properties.Select(p =>
            {

                var args = meta.Properties.Select(subP =>
                        new NamedArgument(subP.Name.FirstToLower(),
                            p == subP
                                ? SyntaxFactory.IdentifierName(subP.Name.FirstToLower())
                                : MemberAccessThis(subP.Name)
                        ))
                    .ToArray();

                return SyntaxFactory
                    .MethodDeclaration(SyntaxFactory.ParseTypeName(meta.Name), $"With{p.Name}")
                    .WithModifiers(Modifiers(SyntaxKind.PublicKeyword))
                    .AddParameterListParameters(FuncParameter(p.Name.FirstToLower(), p.FinalType))
                    .WithBody(SyntaxFactory.Block(SyntaxFactory.ReturnStatement(
                        SyntaxFactory.ObjectCreationExpression(SyntaxFactory.Token(SyntaxKind.NewKeyword),
                            SyntaxFactory.ParseTypeName(meta.Name),
                            ArgumentList(args),
                            initializer: null)

                    )));
            });
        }

        public static IEnumerable GenerateReaderClast(SqModelMeta meta)
        {
            return ExtractTableRefs(meta, out var addName).SelectMany(tableRef => GenerateReaderClast(meta, tableRef, addName));
        }

        public static IEnumerable GenerateReaderClast(SqModelMeta meta, SqModelTableRef tableRef, bool addName)
        {
            string tableType = ExtractTableTypeName(meta, tableRef);
            var clastName = meta.Name + ReaderClastSuffix;
            if (addName)
            {
                clastName += $"For{tableRef.TableTypeName}";
            }
            var clastType = SyntaxFactory.ParseTypeName(clastName);

            var baseInterface = SyntaxFactory.GenericName(
                    SyntaxFactory.Identifier(nameof(ISqModelReader)))
                .WithTypeArgumentList(
                    SyntaxFactory.TypeArgumentList(
                        SyntaxFactory.SeparatedList(
                            new SyntaxNodeOrToken[]
                            {
                                SyntaxFactory.IdentifierName(meta.Name),
                                SyntaxFactory.Token(SyntaxKind.CommaToken),
                                SyntaxFactory.IdentifierName(tableType)
                            })));
            //Instance
            var instance = SyntaxFactory.PropertyDeclaration(
                    clastType,
                    SyntaxFactory.Identifier("Instance"))
                .WithModifiers(
                    SyntaxFactory.TokenList(
                        SyntaxFactory.Token(SyntaxKind.PublicKeyword),
                        SyntaxFactory.Token(SyntaxKind.StaticKeyword)))
                .WithAccessorList(
                    SyntaxFactory.AccessorList(
                        SyntaxFactory.SingletonList(
                            SyntaxFactory.AccessorDeclaration(
                                    SyntaxKind.GetAccessorDeclaration)
                                .WithSemicolonToken(
                                    SyntaxFactory.Token(SyntaxKind.SemicolonToken)))))
                .WithInitializer(
                    SyntaxFactory.EqualsValueClause(
                        SyntaxFactory.ObjectCreationExpression(clastType)
                            .WithArgumentList(SyntaxFactory.ArgumentList())))
                .WithSemicolonToken(
                    SyntaxFactory.Token(SyntaxKind.SemicolonToken));

            //GetColumns
            var getColumns = SyntaxFactory.MethodDeclaration(
                SyntaxFactory.GenericName(
                        SyntaxFactory.Identifier(nameof(IReadOnlyList)))
                    .WithTypeArgumentList(
                        SyntaxFactory.TypeArgumentList(
                            SyntaxFactory.SeparatedList(
                                new SyntaxNodeOrToken[]
                                {
                                    SyntaxFactory.IdentifierName(nameof(ExprColumn))
                                }))),
                    SyntaxFactory.Identifier(MethodNameGetColumns))
                .WithExplicitInterfaceSpecifier(SyntaxFactory.ExplicitInterfaceSpecifier(baseInterface))
                .WithParameterList(
                    SyntaxFactory.ParameterList(
                        SyntaxFactory.SingletonSeparatedList(
                            SyntaxFactory.Parameter(
                                    SyntaxFactory.Identifier("table"))
                                .WithType(
                                    SyntaxFactory.IdentifierName(tableType)))))
                .WithBody(
                    SyntaxFactory.Block(
                        SyntaxFactory.SingletonList(
                            SyntaxFactory.ReturnStatement(
                                SyntaxFactory.InvocationExpression(
                                        SyntaxFactory.MemberAccessExpression(
                                            SyntaxKind.SimpleMemberAccessExpression,
                                            SyntaxFactory.IdentifierName(meta.Name),
                                            SyntaxFactory.IdentifierName(nameof(ISqModelReader.GetColumns))))
                                    .WithArgumentList(
                                        SyntaxFactory.ArgumentList(
                                            SyntaxFactory.SingletonSeparatedList(
                                                SyntaxFactory.Argument(
                                                    SyntaxFactory.IdentifierName("table")))))))));

            //Read
            var read = SyntaxFactory.MethodDeclaration(
                    SyntaxFactory.IdentifierName(meta.Name),
                    SyntaxFactory.Identifier(MethodNameRead))
                .WithExplicitInterfaceSpecifier(SyntaxFactory.ExplicitInterfaceSpecifier(baseInterface))
                .WithParameterList(
                    SyntaxFactory.ParameterList(
                        SyntaxFactory.SeparatedList(
                            new SyntaxNodeOrToken[]
                            {
                                SyntaxFactory.Parameter(
                                        SyntaxFactory.Identifier("record"))
                                    .WithType(
                                        SyntaxFactory.IdentifierName(nameof(ISqDataRecordReader))),
                                SyntaxFactory.Token(SyntaxKind.CommaToken),
                                SyntaxFactory.Parameter(
                                        SyntaxFactory.Identifier("table"))
                                    .WithType(
                                        SyntaxFactory.IdentifierName(tableType))
                            })))
                .WithBody(
                    SyntaxFactory.Block(
                        SyntaxFactory.SingletonList(
                            SyntaxFactory.ReturnStatement(
                                SyntaxFactory.InvocationExpression(
                                        SyntaxFactory.MemberAccessExpression(
                                            SyntaxKind.SimpleMemberAccessExpression,
                                            SyntaxFactory.IdentifierName(meta.Name),
                                            SyntaxFactory.IdentifierName(MethodNameRead)))
                                    .WithArgumentList(
                                        SyntaxFactory.ArgumentList(
                                            SyntaxFactory.SeparatedList(
                                                new SyntaxNodeOrToken[]
                                                {
                                                    SyntaxFactory.Argument(
                                                        SyntaxFactory.IdentifierName("record")),
                                                    SyntaxFactory.Token(SyntaxKind.CommaToken),
                                                    SyntaxFactory.Argument(
                                                        SyntaxFactory.IdentifierName("table"))
                                                })))))));
            //ReadOrdinal
            var readOrdinal = SyntaxFactory.MethodDeclaration(
                    SyntaxFactory.IdentifierName(meta.Name),
                    SyntaxFactory.Identifier(MethodNameReadOrdinal))
                .WithExplicitInterfaceSpecifier(SyntaxFactory.ExplicitInterfaceSpecifier(baseInterface))
                .WithParameterList(
                    SyntaxFactory.ParameterList(
                        SyntaxFactory.SeparatedList(
                            new SyntaxNodeOrToken[]
                            {
                                SyntaxFactory.Parameter(
                                        SyntaxFactory.Identifier("record"))
                                    .WithType(
                                        SyntaxFactory.IdentifierName(nameof(ISqDataRecordReader))),
                                SyntaxFactory.Token(SyntaxKind.CommaToken),
                                SyntaxFactory.Parameter(
                                        SyntaxFactory.Identifier("table"))
                                    .WithType(
                                        SyntaxFactory.IdentifierName(tableType)),                                SyntaxFactory.Token(SyntaxKind.CommaToken),
                                SyntaxFactory.Parameter(
                                        SyntaxFactory.Identifier("offset"))
                                    .WithType(
                                        SyntaxFactory.IdentifierName("int"))
                            })))
                .WithBody(
                    SyntaxFactory.Block(
                        SyntaxFactory.SingletonList(
                            SyntaxFactory.ReturnStatement(
                                SyntaxFactory.InvocationExpression(
                                        SyntaxFactory.MemberAccessExpression(
                                            SyntaxKind.SimpleMemberAccessExpression,
                                            SyntaxFactory.IdentifierName(meta.Name),
                                            SyntaxFactory.IdentifierName(MethodNameReadOrdinal)))
                                    .WithArgumentList(
                                        SyntaxFactory.ArgumentList(
                                            SyntaxFactory.SeparatedList(
                                                new SyntaxNodeOrToken[]
                                                {
                                                    SyntaxFactory.Argument(
                                                        SyntaxFactory.IdentifierName("record")),
                                                    SyntaxFactory.Token(SyntaxKind.CommaToken),
                                                    SyntaxFactory.Argument(
                                                        SyntaxFactory.IdentifierName("table")),
                                                    SyntaxFactory.Token(SyntaxKind.CommaToken),
                                                    SyntaxFactory.Argument(
                                                        SyntaxFactory.IdentifierName("offset"))
                                                })))))));


            var readerClastDeclaration = SyntaxFactory.ClastDeclaration(clastName)
                .WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)))
                .WithBaseList(SyntaxFactory.BaseList().AddTypes(SyntaxFactory.SimpleBaseType(baseInterface)))
                .AddMembers(instance, getColumns, read, readOrdinal);

            var getReader = SyntaxFactory.MethodDeclaration(baseInterface, addName ? $"{MethodNameGetReader}For{tableRef.TableTypeName}" : MethodNameGetReader)
                .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword),
                    SyntaxFactory.Token(SyntaxKind.StaticKeyword))
                .AddBodyStatements(SyntaxFactory.ReturnStatement(MemberAccess(clastName, "Instance")));

            return new MemberDeclarationSyntax[] {getReader, readerClastDeclaration};
        }

        public static IEnumerable GenerateWriterClast(SqModelMeta meta)
        {
            return ExtractTableRefs(meta, out var addName).SelectMany(tableRef => GenerateWriterClast(meta, tableRef, addName));
        }

        public static IEnumerable GenerateWriterClast(SqModelMeta meta, SqModelTableRef tableRef, bool addName)
        {
            if (!HasUpdater(tableRef))
            {
                return Array.Empty();
            }

            var tableType = ExtractTableTypeName(meta, tableRef);
            var clastName = meta.Name + UpdaterClastSuffix;
            if (addName)
            {
                clastName += $"For{tableRef.TableTypeName}";
            }
            var clastType = SyntaxFactory.ParseTypeName(clastName);

            bool hasPk = meta.HasPk();

            var baseInterfaceKeyLess = SyntaxFactory.GenericName(
                    SyntaxFactory.Identifier(nameof(ISqModelUpdater)))
                .WithTypeArgumentList(
                    SyntaxFactory.TypeArgumentList(
                        SyntaxFactory.SeparatedList(
                            new SyntaxNodeOrToken[]
                            {
                                SyntaxFactory.IdentifierName(meta.Name),
                                SyntaxFactory.Token(SyntaxKind.CommaToken),
                                SyntaxFactory.IdentifierName(tableType)
                            })));

            var baseInterfaceKey = SyntaxFactory.GenericName(
                    SyntaxFactory.Identifier(nameof(ISqModelUpdaterKey)))
                .WithTypeArgumentList(
                    SyntaxFactory.TypeArgumentList(
                        SyntaxFactory.SeparatedList(
                            new SyntaxNodeOrToken[]
                            {
                                SyntaxFactory.IdentifierName(meta.Name),
                                SyntaxFactory.Token(SyntaxKind.CommaToken),
                                SyntaxFactory.IdentifierName(tableType)
                            })));
            var baseInterface = hasPk ? baseInterfaceKey : baseInterfaceKeyLess;

            //Instance
            var instance = SyntaxFactory.PropertyDeclaration(
                    clastType,
                    SyntaxFactory.Identifier("Instance"))
                .WithModifiers(
                    SyntaxFactory.TokenList(
                        SyntaxFactory.Token(SyntaxKind.PublicKeyword),
                        SyntaxFactory.Token(SyntaxKind.StaticKeyword)))
                .WithAccessorList(
                    SyntaxFactory.AccessorList(
                        SyntaxFactory.SingletonList(
                            SyntaxFactory.AccessorDeclaration(
                                    SyntaxKind.GetAccessorDeclaration)
                                .WithSemicolonToken(
                                    SyntaxFactory.Token(SyntaxKind.SemicolonToken)))))
                .WithInitializer(
                    SyntaxFactory.EqualsValueClause(
                        SyntaxFactory.ObjectCreationExpression(clastType)
                            .WithArgumentList(SyntaxFactory.ArgumentList())))
                .WithSemicolonToken(
                    SyntaxFactory.Token(SyntaxKind.SemicolonToken));


            //GetMapping
            var dataMapSetterName = "dataMapSetter";

            var parameterDataMapperSetter = SyntaxFactory.Parameter(
                    SyntaxFactory.Identifier(dataMapSetterName))
                .WithType(
                    SyntaxFactory.GenericName(
                            SyntaxFactory.Identifier(nameof(IDataMapSetter)))
                        .WithTypeArgumentList(
                            SyntaxFactory.TypeArgumentList(
                                SyntaxFactory.SeparatedList(
                                    new SyntaxNodeOrToken[]{
                                        SyntaxFactory.IdentifierName(tableType),
                                        SyntaxFactory.Token(SyntaxKind.CommaToken),
                                        SyntaxFactory.IdentifierName(meta.Name)}))));


            var updaterClastDeclaration = SyntaxFactory.ClastDeclaration(clastName)
                .WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)))
                .WithBaseList(SyntaxFactory.BaseList().AddTypes(SyntaxFactory.SimpleBaseType(baseInterface)))
                .AddMembers(
                    instance,
                    GetMapping(baseInterfaceKeyLess, MethodNameGetMapping));

            if (hasPk)
            {
                updaterClastDeclaration = updaterClastDeclaration.AddMembers(
                    GetMapping(baseInterfaceKey, MethodNameGetUpdateKeyMapping),
                    GetMapping(baseInterfaceKey, MethodNameGetUpdateMapping));
            }

            MethodDeclarationSyntax GetMapping(GenericNameSyntax bi, string s)
            {
                return SyntaxFactory.MethodDeclaration(SyntaxFactory.IdentifierName(nameof(IRecordSetterNext)),
                        SyntaxFactory.Identifier(s))
                    .WithExplicitInterfaceSpecifier(SyntaxFactory.ExplicitInterfaceSpecifier(bi))
                    .WithParameterList(
                        SyntaxFactory.ParameterList(SyntaxFactory.SingletonSeparatedList(parameterDataMapperSetter)))
                    .WithBody(
                        SyntaxFactory.Block(
                            SyntaxFactory.SingletonList(
                                SyntaxFactory.ReturnStatement(
                                    SyntaxFactory.InvocationExpression(
                                            SyntaxFactory.MemberAccessExpression(
                                                SyntaxKind.SimpleMemberAccessExpression,
                                                SyntaxFactory.IdentifierName(meta.Name),
                                                SyntaxFactory.IdentifierName(s)))
                                        .WithArgumentList(
                                            SyntaxFactory.ArgumentList(
                                                SyntaxFactory.SingletonSeparatedList(
                                                    SyntaxFactory.Argument(
                                                        SyntaxFactory.IdentifierName(dataMapSetterName)))))))));
            }

            var getUpdater = SyntaxFactory.MethodDeclaration(baseInterface, addName ? $"{MethodNameGetUpdater}For{tableRef.TableTypeName}" : MethodNameGetUpdater)
                .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword),
                    SyntaxFactory.Token(SyntaxKind.StaticKeyword))
                .AddBodyStatements(SyntaxFactory.ReturnStatement(MemberAccess(clastName, "Instance")));

            return new MemberDeclarationSyntax[] { getUpdater, updaterClastDeclaration };
        }

        private static string ExtractTableTypeName(SqModelMeta meta, SqModelTableRef tableRef)
        {
            string tableType = tableRef.TableTypeName;
            if (tableType == meta.Name)
            {
                tableType = $"{tableRef.TableTypeNameSpace}.{tableType}";
            }
            return tableType;
        }

        private static IEnumerable ExtractTableRefs(SqModelMeta meta) 
            => ExtractTableRefs(meta, out _);

        private static IEnumerable ExtractTableRefs(SqModelMeta meta, out bool multi)
        {
            var first = meta.Properties.First();
            multi = first.Column.Count > 1;
            return first.Column.Select(c => c.TableRef);
        }

        private static string ExtractTableColumnTypeName(SqModelTableRef tableRef)
            => tableRef.BaseTypeKindTag.Switch(
                tableBaseRes: nameof(TableColumn), 
                tempTableBaseRes: nameof(TableColumn), 
                derivedTableBaseRes: nameof(ExprColumn));

        private static bool HasUpdater(SqModelTableRef tableRef)
            => tableRef.BaseTypeKindTag.Switch(
                tableBaseRes: true,
                tempTableBaseRes: true,
                derivedTableBaseRes: false);
    }
}