void MoveToNextStatement() { _statementIndex++; if (_statements.Count > _statementIndex) { _statement = _statements[_statementIndex]; _statement.Reset(); } else { _statement = new NpgsqlStatement(); _statements.Add(_statement); } _paramIndexMap.Clear(); _rewrittenSql.Clear(); }
void ParseRawQuery( ReadOnlySpan <char> sql, NpgsqlParameterCollection parameters, List <NpgsqlStatement> statements, bool deriveParameters) { Debug.Assert(deriveParameters == false || parameters.Count == 0); NpgsqlStatement statement = null !; var statementIndex = -1; MoveToNextStatement(); var currCharOfs = 0; var end = sql.Length; var ch = '\0'; int dollarTagStart; int dollarTagEnd; var currTokenBeg = 0; var blockCommentLevel = 0; var parenthesisLevel = 0; None: if (currCharOfs >= end) { goto Finish; } var lastChar = ch; ch = sql[currCharOfs++]; NoneContinue: for (; ; lastChar = ch, ch = sql[currCharOfs++]) { switch (ch) { case '/': goto BlockCommentBegin; case '-': goto LineCommentBegin; case '\'': goto Quoted; case '$': if (!IsIdentifier(lastChar)) { goto DollarQuotedStart; } else { break; } case '"': goto DoubleQuoted; case ':': if (lastChar != ':') { goto ParamStart; } else { break; } case '@': if (lastChar != '@') { goto ParamStart; } else { break; } case ';': if (parenthesisLevel == 0) { goto SemiColon; } break; case '(': parenthesisLevel++; break; case ')': parenthesisLevel--; break; case 'e': case 'E': if (!IsLetter(lastChar)) { goto EscapedStart; } else { break; } } if (currCharOfs >= end) { goto Finish; } } ParamStart: if (currCharOfs < end) { lastChar = ch; ch = sql[currCharOfs]; if (IsParamNameChar(ch)) { if (currCharOfs - 1 > currTokenBeg) { _rewrittenSql.Append(sql.Slice(currTokenBeg, currCharOfs - 1 - currTokenBeg)); } currTokenBeg = currCharOfs++ - 1; goto Param; } currCharOfs++; goto NoneContinue; } goto Finish; Param: // We have already at least one character of the param name for (;;) { lastChar = ch; if (currCharOfs >= end || !IsParamNameChar(ch = sql[currCharOfs])) { var paramName = sql.Slice(currTokenBeg + 1, currCharOfs - (currTokenBeg + 1)).ToString(); if (!_paramIndexMap.TryGetValue(paramName, out var index)) { // Parameter hasn't been seen before in this query if (!parameters.TryGetValue(paramName, out var parameter)) { if (deriveParameters) { parameter = new NpgsqlParameter { ParameterName = paramName }; parameters.Add(parameter); } else { // Parameter placeholder does not match a parameter on this command. // Leave the text as it was in the SQL, it may not be a an actual placeholder _rewrittenSql.Append(sql.Slice(currTokenBeg, currCharOfs - currTokenBeg)); currTokenBeg = currCharOfs; if (currCharOfs >= end) { goto Finish; } currCharOfs++; goto NoneContinue; } } if (!parameter.IsInputDirection) { throw new Exception($"Parameter '{paramName}' referenced in SQL but is an out-only parameter"); } statement.InputParameters.Add(parameter); index = _paramIndexMap[paramName] = statement.InputParameters.Count; } _rewrittenSql.Append('$'); _rewrittenSql.Append(index); currTokenBeg = currCharOfs; if (currCharOfs >= end) { goto Finish; } currCharOfs++; goto NoneContinue; } currCharOfs++; } Quoted: while (currCharOfs < end) { if (sql[currCharOfs++] == '\'') { ch = '\0'; goto None; } } goto Finish; DoubleQuoted: while (currCharOfs < end) { if (sql[currCharOfs++] == '"') { ch = '\0'; goto None; } } goto Finish; EscapedStart: if (currCharOfs < end) { lastChar = ch; ch = sql[currCharOfs++]; if (ch == '\'') { goto Escaped; } goto NoneContinue; } goto Finish; Escaped: while (currCharOfs < end) { ch = sql[currCharOfs++]; switch (ch) { case '\'': goto MaybeConcatenatedEscaped; case '\\': { if (currCharOfs >= end) { goto Finish; } currCharOfs++; break; } } } goto Finish; MaybeConcatenatedEscaped: while (currCharOfs < end) { ch = sql[currCharOfs++]; switch (ch) { case '\r': case '\n': goto MaybeConcatenatedEscaped2; case ' ': case '\t': case '\f': continue; default: lastChar = '\0'; goto NoneContinue; } } goto Finish; MaybeConcatenatedEscaped2: while (currCharOfs < end) { ch = sql[currCharOfs++]; switch (ch) { case '\'': goto Escaped; case '-': { if (currCharOfs >= end) { goto Finish; } ch = sql[currCharOfs++]; if (ch == '-') { goto MaybeConcatenatedEscapeAfterComment; } lastChar = '\0'; goto NoneContinue; } case ' ': case '\t': case '\n': case '\r': case '\f': continue; default: lastChar = '\0'; goto NoneContinue; } } goto Finish; MaybeConcatenatedEscapeAfterComment: while (currCharOfs < end) { ch = sql[currCharOfs++]; if (ch == '\r' || ch == '\n') { goto MaybeConcatenatedEscaped2; } } goto Finish; DollarQuotedStart: if (currCharOfs < end) { ch = sql[currCharOfs]; if (ch == '$') { // Empty tag dollarTagStart = dollarTagEnd = currCharOfs; currCharOfs++; goto DollarQuoted; } if (IsIdentifierStart(ch)) { dollarTagStart = currCharOfs; currCharOfs++; goto DollarQuotedInFirstDelim; } lastChar = '$'; currCharOfs++; goto NoneContinue; } goto Finish; DollarQuotedInFirstDelim: while (currCharOfs < end) { lastChar = ch; ch = sql[currCharOfs++]; if (ch == '$') { dollarTagEnd = currCharOfs - 1; goto DollarQuoted; } if (!IsDollarTagIdentifier(ch)) { goto NoneContinue; } } goto Finish; DollarQuoted: var tag = sql.Slice(dollarTagStart - 1, dollarTagEnd - dollarTagStart + 2); var pos = sql.Slice(dollarTagEnd + 1).IndexOf(tag); if (pos == -1) { currCharOfs = end; goto Finish; } pos += dollarTagEnd + 1; // If the substring is found adjust the position to be relative to the entire span currCharOfs = pos + dollarTagEnd - dollarTagStart + 2; ch = '\0'; goto None; LineCommentBegin: if (currCharOfs < end) { ch = sql[currCharOfs++]; if (ch == '-') { goto LineComment; } lastChar = '\0'; goto NoneContinue; } goto Finish; LineComment: while (currCharOfs < end) { ch = sql[currCharOfs++]; if (ch == '\r' || ch == '\n') { goto None; } } goto Finish; BlockCommentBegin: while (currCharOfs < end) { ch = sql[currCharOfs++]; if (ch == '*') { blockCommentLevel++; goto BlockComment; } if (ch != '/') { if (blockCommentLevel > 0) { goto BlockComment; } lastChar = '\0'; goto NoneContinue; } } goto Finish; BlockComment: while (currCharOfs < end) { ch = sql[currCharOfs++]; switch (ch) { case '*': goto BlockCommentEnd; case '/': goto BlockCommentBegin; } } goto Finish; BlockCommentEnd: while (currCharOfs < end) { ch = sql[currCharOfs++]; if (ch == '/') { if (--blockCommentLevel > 0) { goto BlockComment; } goto None; } if (ch != '*') { goto BlockComment; } } goto Finish; SemiColon: _rewrittenSql.Append(sql.Slice(currTokenBeg, currCharOfs - currTokenBeg - 1)); statement.SQL = _rewrittenSql.ToString(); while (currCharOfs < end) { ch = sql[currCharOfs]; if (char.IsWhiteSpace(ch)) { currCharOfs++; continue; } // TODO: Handle end of line comment? Although psql doesn't seem to handle them... currTokenBeg = currCharOfs; if (_rewrittenSql.Length > 0) { MoveToNextStatement(); } goto None; } if (statements.Count > statementIndex + 1) { statements.RemoveRange(statementIndex + 1, statements.Count - (statementIndex + 1)); } return; Finish: _rewrittenSql.Append(sql.Slice(currTokenBeg, end - currTokenBeg)); statement.SQL = _rewrittenSql.ToString(); if (statements.Count > statementIndex + 1) { statements.RemoveRange(statementIndex + 1, statements.Count - (statementIndex + 1)); } void MoveToNextStatement() { statementIndex++; if (statements.Count > statementIndex) { statement = statements[statementIndex]; statement.Reset(); } else { statement = new NpgsqlStatement(); statements.Add(statement); } _paramIndexMap.Clear(); _rewrittenSql.Clear(); } }