Implemented ArrayParser and unit tests to verify its working.

This commit is contained in:
eelke 2017-12-16 10:31:51 +01:00
parent b5d800c87e
commit ec930218cd
4 changed files with 254 additions and 3 deletions

102
pgsql/ArrayParser.cpp Normal file
View file

@ -0,0 +1,102 @@
#include "ArrayParser.h"
#include <stdexcept>
using namespace Pgsql;
namespace {
constexpr char ArrayStart = '{';
constexpr char ArrayEnd = '}';
constexpr char Quote = '"';
constexpr char Seperator = ',';
}
ArrayParser::ArrayParser(const char *array_string)
: data(array_string)
, end(array_string + strlen(array_string))
, pos(array_string)
{
initializeParse();
}
ArrayParser::NextElemResult ArrayParser::GetNextElem()
{
// We should be at the start of an element or at the end of the array
NextElemResult result = { false, std::nullopt };
if (pos < end && *pos != ArrayEnd) {
if (*pos == Quote) {
// parse quoted value, slow path removing escapes
parseQuotedValue();
result.ok = true;
result.value = std::string_view(temp);
}
else {
// parse unquoted value, fast path no escapes
const char *start = pos;
while (pos < end && *pos != Seperator && *pos != ArrayEnd) ++pos;
if (*pos == 0) // reached end of data shouldn't happen
throw std::runtime_error("Invalid input");
result.ok = true;
if ((pos - start) != 4 || std::strncmp(start, "NULL", 4) != 0)
result.value = std::string_view(start, pos-start);
}
// move to start of next element
++pos; // skip seperator
skipWhitespace();
}
return result;
}
void ArrayParser::parseQuotedValue()
{
std::string s;
// internal function thus we can safely assumed the caller already verified
// the opening quote
++pos;
if (pos == end)
throw std::runtime_error("Invalid input");
while (pos < end) {
if (*pos == Quote) {
++pos;
break;
}
if (*pos == '\\') {
++pos;
if (pos == end)
throw std::runtime_error("Invalid input");
}
s += *pos;
++pos;
}
temp = std::move(s);
}
void ArrayParser::initializeParse()
{
// Test if non empty string (empty string is an empty array)
//
skipWhitespace();
if (pos < end) {
// first character should be opening brace
if (*pos != ArrayStart) {
throw std::runtime_error("Unexpected input");
}
++pos;
skipWhitespace();
// Position is now first element or end of array when the array is empty
// GetNextElem can take it from here
}
}
inline void ArrayParser::skipWhitespace()
{
while (pos < end && (*pos == ' ' || *pos == '\t')) ++pos;
}

53
pgsql/ArrayParser.h Normal file
View file

@ -0,0 +1,53 @@
#ifndef ARRAYPARSER_H
#define ARRAYPARSER_H
#include <optional>
#include <tuple>
#include <string>
#include <string_view>
namespace Pgsql {
/** Class for parsing array values coming from postgresql
*
* exceptions are used to report serious errors
* in production these kind of errors should rarely happen as
* they are either a bug in this parser or postgres changed its format.
*/
class ArrayParser {
public:
/**
* \param data The string that needs parsing (warning just the pointer is stored, the string is not copied)
*/
explicit ArrayParser(const char *array_string);
class NextElemResult {
public:
bool ok;
std::optional<std::string_view> value;
};
/**
*
* Usage:
* auto [ok, val] = parser.GetNextElem();
*
* \return the bool signals if there was a next element when it is true or the end of the array in which
* case it is false. The optional is not set when the next element IS NULL. Otherwise it refers to the
* string value of the element. If the element was quoted it has been stripped of quotes and escapes.
*/
NextElemResult GetNextElem();
private:
const char *data;
const char *end;
std::string temp; // internal buffer for when a value needs escaping
const char *pos;
void parseQuotedValue();
void initializeParse();
/** Moves pos forward to the first non whitespace character.
*/
void skipWhitespace();
};
} // end namespace Pgsql
#endif // ARRAYPARSER_H

View file

@ -4,7 +4,7 @@
#
#-------------------------------------------------
CONFIG += staticlib c++14
CONFIG += staticlib c++17
QT += core
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets sql
@ -16,6 +16,8 @@ INCLUDEPATH += C:\prog\include \
C:\Prog\include\pgsql \
C:\VSproj\boost32\include\boost-1_65_1
QMAKE_CXXFLAGS += /std:c++17
DEFINES += WIN32_LEAN_AND_MEAN NOMINMAX
#LIBS += -LC:/prog/boost/lib -Lc:/prog/lib libpq.lib fmt.lib User32.lib ws2_32.lib
LIBS += -LC:/PROG/LIB -lws2_32 -llibpq
@ -35,7 +37,8 @@ SOURCES += Pgsql_Connection.cpp \
Pgsql_Result.cpp \
Pgsql_Row.cpp \
Pgsql_Value.cpp \
Pgsql_Col.cpp
Pgsql_Col.cpp \
ArrayParser.cpp
HEADERS += Pgsql_Connection.h \
Pgsql_Params.h \
@ -43,7 +46,8 @@ HEADERS += Pgsql_Connection.h \
Pgsql_Row.h \
Pgsql_Value.h \
Pgsql_declare.h \
Pgsql_Col.h
Pgsql_Col.h \
ArrayParser.h
#FORMS +=