/// <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); } } }