string _userRawSql; //raw sql from user code
        public SqlStringTemplate(string rawSql)
        {
            _userRawSql = rawSql;
            //------------------------------
            //parse
            int length = rawSql.Length;
            ParseState state = ParseState.FIND_MARKER;
            StringBuilder stBuilder = new StringBuilder();
            //TODO: review parser state, escape ' or " or `

            char binderEscapeChar = '\0';
            char escapeChar = '\0';
            for (int i = 0; i < length; i++)
            {
                char ch = rawSql[i];
                switch (state)
                {
                    default:
                        //unknown state must throw exception, so we can see if something changed
                        throw new NotSupportedException();
                    case ParseState.FIND_MARKER:

                        if (ch == '?' || ch == '@')
                        {
                            binderEscapeChar = ch;
                            //found begining point of new marker
                            if (stBuilder.Length > 0)
                            {
                                _sqlSections.Add(new SqlSection(stBuilder.ToString(), SqlSectionKind.SqlText));
                                stBuilder.Length = 0;
                            }
                            state = ParseState.COLLECT_MARKER_KEY;
                        }
                        else if (ch == '\'' || ch == '"' || ch == '`')
                        {
                            escapeChar = ch;
                            state = ParseState.STRING_ESCAPE;
                        }

                        stBuilder.Append(ch);
                        break;
                    case ParseState.COLLECT_MARKER_KEY:

                        if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_')
                        {
                            stBuilder.Append(ch);
                        }
                        else if (ch == '?')
                        {
                            //this is special marker key
                            stBuilder.Append(ch);
                            if (binderEscapeChar == '?')
                            {
                                state = ParseState.COLLECT_SP_MARKER_KEY;
                            }
                            else
                            {
                                //eg ?@
                                //error
                                throw new NotSupportedException("syntax err!");
                            }
                        }
                        else if (ch == '@')
                        {
                            stBuilder.Append(ch);
                            if (binderEscapeChar == '@')
                            {
                                //@@ 
                                state = ParseState.FIND_MARKER; //goto normal text state
                            }
                            else
                            {
                                //eg @?
                                //eg ?@
                                //error
                                throw new NotSupportedException("syntax err!");
                            }
                        }
                        else
                        {
                            //value binding marking end here

                            if (stBuilder.Length > 0)
                            {
                                var valueSection = new SqlBoundSection(stBuilder.ToString());
                                _sqlSections.Add(valueSection);
                                _valuesKeys.Add(valueSection);
                                stBuilder.Length = 0;
                            }
                            state = ParseState.FIND_MARKER;
                            stBuilder.Append(ch);
                        }
                        break;
                    case ParseState.COLLECT_SP_MARKER_KEY:
                        if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_')
                        {
                            stBuilder.Append(ch);
                        }
                        else
                        {
                            //special marker end here
                            if (stBuilder.Length > 0)
                            {
                                var specialSection = new SqlSection(stBuilder.ToString(), SqlSectionKind.SpecialKey);
                                _sqlSections.Add(specialSection);
                                _specialKeys.Add(specialSection);
                                stBuilder.Length = 0;
                            }
                            state = ParseState.FIND_MARKER;
                            stBuilder.Append(ch);
                        }
                        break;
                    case ParseState.STRING_ESCAPE:
                        {
                            if (ch == '\'' || ch == '"' || ch == '`')
                            {
                                if (escapeChar == ch)
                                {
                                    escapeChar = '\0';
                                    //go back to find marker state
                                    state = ParseState.FIND_MARKER;
                                }
                            }
                            stBuilder.Append(ch);
                        }
                        break;
                }//end swicth
            }//end for


            if (stBuilder.Length > 0)
            {
                switch (state)
                {
                    default:
                        throw new NotSupportedException();
                    case ParseState.FIND_MARKER:
                        _sqlSections.Add(new SqlSection(stBuilder.ToString(), SqlSectionKind.SqlText));
                        break;
                    case ParseState.COLLECT_MARKER_KEY:
                        var valueSection = new SqlBoundSection(stBuilder.ToString());
                        _sqlSections.Add(valueSection);
                        _valuesKeys.Add(valueSection);
                        break;
                    case ParseState.COLLECT_SP_MARKER_KEY:
                        var specialSection = new SqlSection(stBuilder.ToString(), SqlSectionKind.SpecialKey);
                        _sqlSections.Add(specialSection);
                        _specialKeys.Add(specialSection);
                        break;
                }
            }
        }
        string _userRawSql; //raw sql from user code

        public SqlStringTemplate(string rawSql)
        {
            _userRawSql = rawSql;
            //------------------------------
            //parse
            int           length    = rawSql.Length;
            ParseState    state     = ParseState.FIND_MARKER;
            StringBuilder stBuilder = new StringBuilder();

            //TODO: review parser state, escape ' or " or `

            char binderEscapeChar = '\0';
            char escapeChar       = '\0';

            for (int i = 0; i < length; i++)
            {
                char ch = rawSql[i];
                switch (state)
                {
                default:
                    //unknown state must throw exception, so we can see if something changed
                    throw new NotSupportedException();

                case ParseState.FIND_MARKER:

                    if (ch == '?' || ch == '@')
                    {
                        binderEscapeChar = ch;

                        //found begining point of new marker
                        if (stBuilder.Length > 0)
                        {
                            _sqlSections.Add(new SqlSection(stBuilder.ToString(), SqlSectionKind.SqlText));
                            stBuilder.Length = 0;
                        }
                        state = ParseState.COLLECT_MARKER_KEY;
                    }
                    else if (ch == '\'' || ch == '"' || ch == '`')
                    {
                        escapeChar = ch;
                        state      = ParseState.STRING_ESCAPE;
                    }

                    stBuilder.Append(ch);
                    break;

                case ParseState.COLLECT_MARKER_KEY:

                    if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_')
                    {
                        stBuilder.Append(ch);
                    }
                    else if (ch == '?')
                    {
                        //this is special marker key
                        stBuilder.Append(ch);
                        if (binderEscapeChar == '?')
                        {
                            state = ParseState.COLLECT_SP_MARKER_KEY;
                        }
                        else
                        {
                            //eg ?@
                            //error
                            throw new NotSupportedException("syntax err!");
                        }
                    }
                    else if (ch == '@')
                    {
                        stBuilder.Append(ch);
                        if (binderEscapeChar == '@')
                        {
                            //@@
                            state = ParseState.FIND_MARKER;     //goto normal text state
                        }
                        else
                        {
                            //eg @?
                            //eg ?@
                            //error
                            throw new NotSupportedException("syntax err!");
                        }
                    }
                    else
                    {
                        //value binding marking end here

                        if (stBuilder.Length > 0)
                        {
                            var valueSection = new SqlBoundSection(stBuilder.ToString());
                            _sqlSections.Add(valueSection);
                            _valuesKeys.Add(valueSection);

                            stBuilder.Length = 0;
                        }
                        state = ParseState.FIND_MARKER;
                        stBuilder.Append(ch);
                    }
                    break;

                case ParseState.COLLECT_SP_MARKER_KEY:
                    if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_')
                    {
                        stBuilder.Append(ch);
                    }
                    else
                    {
                        //special marker end here
                        if (stBuilder.Length > 0)
                        {
                            var specialSection = new SqlSection(stBuilder.ToString(), SqlSectionKind.SpecialKey);
                            _sqlSections.Add(specialSection);
                            _specialKeys.Add(specialSection);
                            stBuilder.Length = 0;
                        }
                        state = ParseState.FIND_MARKER;

                        stBuilder.Append(ch);
                    }
                    break;

                case ParseState.STRING_ESCAPE:
                {
                    if (ch == '\'' || ch == '"' || ch == '`')
                    {
                        if (escapeChar == ch)
                        {
                            escapeChar = '\0';
                            //go back to find marker state
                            state = ParseState.FIND_MARKER;
                        }
                    }
                    stBuilder.Append(ch);
                }
                break;
                } //end swicth
            }     //end for


            if (stBuilder.Length > 0)
            {
                switch (state)
                {
                default:
                    throw new NotSupportedException();

                case ParseState.FIND_MARKER:
                    _sqlSections.Add(new SqlSection(stBuilder.ToString(), SqlSectionKind.SqlText));
                    break;

                case ParseState.COLLECT_MARKER_KEY:
                    var valueSection = new SqlBoundSection(stBuilder.ToString());
                    _sqlSections.Add(valueSection);
                    _valuesKeys.Add(valueSection);
                    break;

                case ParseState.COLLECT_SP_MARKER_KEY:
                    var specialSection = new SqlSection(stBuilder.ToString(), SqlSectionKind.SpecialKey);
                    _sqlSections.Add(specialSection);
                    _specialKeys.Add(specialSection);
                    break;
                }
            }
        }