Example #1
0
        /// <summary>
        /// split PqsqlCommand.CommandText into an array of sub-statements
        /// </summary>
        /// <returns></returns>
        private IEnumerable <string> ParseStatements()
        {
#if CODECONTRACTS
            Contract.Ensures(Contract.Result <IEnumerable <string> >() != null);
#endif

            IntPtr pstate   = IntPtr.Zero;
            IntPtr varArray = IntPtr.Zero;

            try
            {
                unsafe
                {
                    int offset = 0;
                    varArray = Marshal.AllocHGlobal((mParams.Count + 1) * sizeof(sbyte *));

                    // always write NULL before we continue, so finally clause can clean up properly
                    // if we get hit by an exception
                    Marshal.WriteIntPtr(varArray, offset, IntPtr.Zero);

                    foreach (PqsqlParameter param in mParams)
                    {
                        // always write NULL before we continue, so finally clause can clean up properly
                        // if we get hit by an exception
                        Marshal.WriteIntPtr(varArray, offset, IntPtr.Zero);

                        string psqlParamName = param.PsqlParameterName;

                        // psql-specific: characters allowed in variable names: [A-Za-z\200-\377_0-9]
                        // we only allow lowercase [a-z0-9_], as PsqlParameter always stores parameter names in lowercase
                        char invalid = psqlParamName.FirstOrDefault(c => !(c >= 'a' && c <= 'z') && !char.IsDigit(c) && c != '_');
                        if (invalid != default(char))
                        {
                            string msg = string.Format(CultureInfo.InvariantCulture, "Parameter name «{0}» contains invalid character «{1}»", psqlParamName, invalid);
                            throw new PqsqlException(msg, (int)PqsqlState.SYNTAX_ERROR);
                        }

                        // variable names are pure ascii
                        byte[] paramNameArray = Encoding.ASCII.GetBytes(psqlParamName);
                        int    len            = paramNameArray.Length;

                        // we need a null-terminated variable string
                        IntPtr varString = Marshal.AllocHGlobal(len + 1);
                        Marshal.Copy(paramNameArray, 0, varString, len);
                        Marshal.WriteByte(varString, len, 0);

                        Marshal.WriteIntPtr(varArray, offset, varString);
                        offset += sizeof(sbyte *);
                    }

                    Marshal.WriteIntPtr(varArray, offset, IntPtr.Zero);
                }

                // varArray pointers must be valid during parsing
                pstate = PqsqlBinaryFormat.pqparse_init(varArray);

                // always terminate CommandText with a ; (prevents unnecessary re-parsing)
                // we have the following cases:
                // 1) "select 1; select 2"
                // 2) "select 1; select 2 -- dash-dash comment forces newline for semicolon"
                // 3) "select 1; select 2; /* slash-star comment forces unnecessary semicolon */"
                // 4) "select 1; select 2 -- dash-dash comment triggers ;"
                //
                // For (1), (2), (3) we simply add a newline + semicolon.  Case (4) is more tricky
                // and requires to re-start the parser for another round.
                string commands = CommandText.TrimEnd();
                if (!commands.EndsWith(";", StringComparison.Ordinal))
                {
                    commands += "\n;";
                }

                byte[] statementsString = PqsqlUTF8Statement.CreateUTF8Statement(commands);

                // add a semicolon-separated list of UTF-8 statements
                int parsingState = PqsqlBinaryFormat.pqparse_add_statements(pstate, statementsString);

                if (parsingState == -1)                 // syntax error or missing parameter
                {
                    ParsingError(pstate);
                }
                else if (parsingState == 1)                 // incomplete input, continue with current parsing state and force final "\n;"
                {
                    statementsString = PqsqlUTF8Statement.CreateUTF8Statement("\n;");
                    if (PqsqlBinaryFormat.pqparse_add_statements(pstate, statementsString) != 0)
                    {
                        ParsingError(pstate);                         // syntax error / missing parameter / incomplete input
                    }
                }

                uint num = PqsqlBinaryFormat.pqparse_num_statements(pstate);

                string[] statements = new string[num];

                // the null-terminated array of UTF-8 statement strings
                IntPtr sptr = PqsqlBinaryFormat.pqparse_get_statements(pstate);

                if (num > 0 && sptr != IntPtr.Zero)
                {
                    unsafe
                    {
                        for (int i = 0; i < num; i++)
                        {
                            sbyte **stm = (sbyte **)sptr.ToPointer();

                            if (stm == null || *stm == null)
                            {
                                break;
                            }

                            // convert UTF-8 to UTF-16
                            statements[i] = PqsqlUTF8Statement.CreateStringFromUTF8(new IntPtr(*stm));
                            sptr          = IntPtr.Add(sptr, sizeof(sbyte *));
                        }
                    }
                }

#if CODECONTRACTS
                Contract.Assert(statements != null);
                Contract.Assert(statements.Length == num);
#endif

                return(from statement in statements
                       where !string.IsNullOrWhiteSpace(statement) && statement != ";"
                       select statement);
            }
            finally
            {
                if (pstate != IntPtr.Zero)
                {
                    PqsqlBinaryFormat.pqparse_destroy(pstate);
                }

                if (varArray != IntPtr.Zero)
                {
                    unsafe
                    {
                        for (int i = mParams.Count - 1; i >= 0; i--)
                        {
                            IntPtr varPtr = Marshal.ReadIntPtr(varArray, i * sizeof(sbyte *));

                            if (varPtr != IntPtr.Zero)
                            {
                                Marshal.FreeHGlobal(varPtr);
                            }
                        }
                    }
                    Marshal.FreeHGlobal(varArray);
                }
            }
        }