src/SqlInputScanner.h
branchv_0
changeset 33 86ceb97db7de
equal deleted inserted replaced
32:77180ee275df 33:86ceb97db7de
       
     1 /**
       
     2  * Relational pipes
       
     3  * Copyright © 2020 František Kučera (Frantovo.cz, GlobalCode.info)
       
     4  *
       
     5  * This program is free software: you can redistribute it and/or modify
       
     6  * it under the terms of the GNU General Public License as published by
       
     7  * the Free Software Foundation, version 3.
       
     8  *
       
     9  * This program is distributed in the hope that it will be useful,
       
    10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
       
    12  * GNU General Public License for more details.
       
    13  *
       
    14  * You should have received a copy of the GNU General Public License
       
    15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
       
    16  */
       
    17 #pragma once
       
    18 
       
    19 #include <sstream>
       
    20 #include <stdexcept>
       
    21 #include <string>
       
    22 
       
    23 namespace relpipe {
       
    24 namespace tr {
       
    25 namespace sql {
       
    26 
       
    27 /**
       
    28  * An SQL script may consist of several SQL statements separated by „;“ character.
       
    29  * But this character may also occur in literal values or quoted identifiers where it does not separate SQL statements.
       
    30  * This scanner reads SQL script character by character and separates particular SQL statements.
       
    31  * It recognizes comments, literals, quoted values and other parts (see enum State),
       
    32  * so it is able to distinguish which „;“ separates SQL statements from other „;“ that should be ignored.
       
    33  * 
       
    34  * Usage:
       
    35  * Append characters using append() and when it returns true,
       
    36  * call getAndReset() and use the returned statement.
       
    37  * If there are any remaining characters on the input, start again with appending…
       
    38  */
       
    39 class SqlInputScanner {
       
    40 private:
       
    41 
       
    42 	enum class State {
       
    43 		/** SQL code like keywords, functions, operators or identifiers (not quoted ones) */
       
    44 		SQL,
       
    45 		/** classic SQL comment: -- terminated by the line end */
       
    46 		COMMENT_SINGLE,
       
    47 		/** comment like this one */
       
    48 		COMMENT_MULTI,
       
    49 		/** quoted identifier like "column_name" */
       
    50 		IDENTIFIER,
       
    51 		/** string literal like 'some value' */
       
    52 		STRING_VALUE
       
    53 	};
       
    54 
       
    55 	State state = State::SQL;
       
    56 	bool includeSeparators = true;
       
    57 	std::wstringstream current;
       
    58 	wchar_t last = L'\0';
       
    59 
       
    60 public:
       
    61 	/**
       
    62 	 * @param ch next character from the input stream
       
    63 	 * @return whether the accumulated SQL statement is complete – then call getAndReset(), otherwise continue appending
       
    64 	 */
       
    65 	bool append(wchar_t ch);
       
    66 
       
    67 	/**
       
    68 	 * Returns current buffer and empties it, so append() can be called again (building new SQL statement)
       
    69 	 * @return accumulated SQL statement
       
    70 	 */
       
    71 	std::wstring getAndReset();
       
    72 
       
    73 	/**
       
    74 	 * @param includeSeparators whether „;“ separators should be appended to the buffer
       
    75 	 * and then returned from getAndReset() as part of the SQL statement
       
    76 	 */
       
    77 	void setIncludeSeparators(bool includeSeparators) {
       
    78 		this->includeSeparators = includeSeparators;
       
    79 	}
       
    80 };
       
    81 
       
    82 bool SqlInputScanner::append(wchar_t ch) {
       
    83 	if (state != State::SQL || (ch != L';') || (includeSeparators && ch == L';')) current << ch;
       
    84 
       
    85 	if (state == State::SQL) {
       
    86 		if (ch == L'"') state = State::IDENTIFIER;
       
    87 		else if (ch == L'\'') state = State::STRING_VALUE;
       
    88 		else if (ch == L'-' && last == L'-') state = State::COMMENT_SINGLE;
       
    89 		else if (ch == L'*' && last == L'/') state = State::COMMENT_MULTI;
       
    90 	} else if (state == State::COMMENT_SINGLE) {
       
    91 		if (ch == L'\n') state = State::SQL;
       
    92 	} else if (state == State::COMMENT_MULTI) {
       
    93 		if (ch == L'/' && last == L'*') state = State::SQL;
       
    94 	} else if (state == State::IDENTIFIER) {
       
    95 		if (ch == L'"') state = State::SQL;
       
    96 	} else if (state == State::STRING_VALUE) {
       
    97 		if (ch == L'\'') state = State::SQL;
       
    98 	} else {
       
    99 		throw std::domain_error("Unsupported SqlInputScanner state (bug in code)");
       
   100 	}
       
   101 
       
   102 	last = ch;
       
   103 	return state == State::SQL && ch == L';';
       
   104 };
       
   105 
       
   106 std::wstring SqlInputScanner::getAndReset() {
       
   107 	std::wstring str = current.str();
       
   108 	current.str(L"");
       
   109 	current.clear();
       
   110 	return str;
       
   111 }
       
   112 
       
   113 }
       
   114 }
       
   115 }