Commit 1655a195 authored by Maciej Sobczak's avatar Maciej Sobczak

Added bigstring (XML and CLOB) support.

parent d6817049
......@@ -15,6 +15,7 @@
* [Extending with user-provided datatypes](#custom_types)
* [Object-relational mapping](#object_relational)
* [Large objects (BLOBs)](#blob)
* [Long strings and XML](#xml)
### <a name="bind_local"></a> Binding local data
......@@ -566,4 +567,32 @@ The `offset` parameter is always counted from the beginning of the BLOB's data.
##### Portability notes:
* The way to define BLOB table columns and create or destroy BLOB objects in the database varies between different database engines. Please see the SQL documentation relevant for the given server to learn how this is actually done. The test programs provided with the SOCI library can be also a simple source of full working examples.
* The `trim` function is not currently available for the PostgreSQL backend.
\ No newline at end of file
* The `trim` function is not currently available for the PostgreSQL backend.
### <a name="xml"></a> Long strings and XML
The SOCI library recognizes the fact that long string values are not handled portably and in some databases long string values need to be stored as a different data type. Similar concerns relate to the use of XML values, which are essentially strings at the application level, but can be stored in special database-level field types.
In order to facilitate handling of long strings and XML values the following wrapper types are defined:
struct xml_type
{
std::string value;
};
struct long_string
{
std::string value;
};
Values of these wrapper types can be used with `into` and `use` elements with the database target type that is specifically intended to handle XML and long strings data types.
For Oracle, these database-side types are, respectively:
*`XMLType`,
*`CLOB`
For PostgreSQL, these types are:
*`XML`
*`text`
......@@ -63,7 +63,8 @@ struct type_conversion<column_info>
ci.scale = get_numeric_value(v, "NUMERIC_SCALE");
const std::string & type_name = v.get<std::string>("DATA_TYPE");
if (type_name == "text" ||
if (type_name == "text" || type_name == "TEXT" ||
type_name == "clob" || type_name == "CLOB" ||
type_name.find("char") != std::string::npos ||
type_name.find("CHAR") != std::string::npos)
{
......@@ -96,11 +97,16 @@ struct type_conversion<column_info>
}
else if (type_name.find("blob") != std::string::npos ||
type_name.find("BLOB") != std::string::npos ||
type_name.find("bytea") != std::string::npos ||
type_name.find("BYTEA") != std::string::npos)
type_name.find("oid") != std::string::npos ||
type_name.find("OID") != std::string::npos)
{
ci.type = dt_blob;
}
else if (type_name.find("xml") != std::string::npos ||
type_name.find("XML") != std::string::npos)
{
ci.type = dt_xml;
}
else
{
// this seems to be a safe default
......
......@@ -10,6 +10,7 @@
#include "soci/type-conversion-traits.h"
#include "soci/soci-backend.h"
#include "soci/type-wrappers.h"
// std
#include <ctime>
#include <string>
......@@ -131,6 +132,22 @@ struct exchange_traits<std::vector<T> >
enum { x_type = exchange_traits<T>::x_type };
};
// handling of wrapper types
template <>
struct exchange_traits<xml_type>
{
typedef basic_type_tag type_family;
enum { x_type = x_xmltype };
};
template <>
struct exchange_traits<long_string>
{
typedef basic_type_tag type_family;
enum { x_type = x_longstring };
};
} // namespace details
} // namespace soci
......
......@@ -58,6 +58,8 @@ struct oracle_standard_into_type_backend : details::standard_into_type_backend
virtual void define_by_pos(int &position,
void *data, details::exchange_type type);
void read_from_lob(OCILobLocator * lobp, std::string & value);
virtual void pre_fetch();
virtual void post_fetch(bool gotData, bool calledFromFetch,
indicator *ind);
......@@ -69,6 +71,7 @@ struct oracle_standard_into_type_backend : details::standard_into_type_backend
OCIDefine *defnp_;
sb2 indOCIHolder_;
void *data_;
void *ociData_;
char *buf_; // generic buffer
details::exchange_type type_;
......@@ -137,6 +140,9 @@ struct oracle_standard_use_type_backend : details::standard_use_type_backend
// common part for bind_by_pos and bind_by_name
void prepare_for_bind(void *&data, sb4 &size, ub2 &oracleType, bool readOnly);
// common helper for pre_use for LOB-directed wrapped types
void write_to_lob(OCILobLocator * lobp, const std::string & value);
virtual void pre_use(indicator const *ind);
virtual void post_use(bool gotData, indicator *ind);
......@@ -147,6 +153,7 @@ struct oracle_standard_use_type_backend : details::standard_use_type_backend
OCIBind *bindp_;
sb2 indOCIHolder_;
void *data_;
void *ociData_;
bool readOnly_;
char *buf_; // generic buffer
details::exchange_type type_;
......@@ -379,6 +386,13 @@ struct oracle_session_backend : details::session_backend
case dt_blob:
res += "blob";
break;
case dt_xml:
res += "xmltype";
break;
default:
throw soci_error("this data_type is not supported in create_column");
}
return res;
......
......@@ -22,7 +22,8 @@ namespace soci
// data types, as seen by the user
enum data_type
{
dt_string, dt_date, dt_double, dt_integer, dt_long_long, dt_unsigned_long_long, dt_blob
dt_string, dt_date, dt_double, dt_integer, dt_long_long, dt_unsigned_long_long,
dt_blob, dt_xml
};
// the enum type for indicator variables
......@@ -47,7 +48,10 @@ enum exchange_type
x_stdtm,
x_statement,
x_rowid,
x_blob
x_blob,
x_xmltype,
x_longstring
};
// type of statement (used for optimizing statement preparation)
......@@ -361,8 +365,15 @@ public:
break;
case dt_blob:
res += "bytea";
res += "oid";
break;
case dt_xml:
res += "xml";
break;
default:
throw soci_error("this data_type is not supported in create_column");
}
return res;
......@@ -372,7 +383,7 @@ public:
int precision, int scale)
{
return "alter table " + tableName + " add column " + columnName +
create_column_type(dt, precision, scale);
" " + create_column_type(dt, precision, scale);
}
virtual std::string alter_column(const std::string & tableName,
const std::string & columnName, data_type dt,
......
......@@ -37,6 +37,7 @@
#include "soci/type-conversion-traits.h"
#include "soci/type-holder.h"
#include "soci/type-ptr.h"
#include "soci/type-wrappers.h"
#include "soci/unsigned-types.h"
#include "soci/use.h"
#include "soci/use-type.h"
......
//
// Copyright (C) 2016 Maciej Sobczak
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef SOCI_TYPE_WRAPPERS_H_INCLUDED
#define SOCI_TYPE_WRAPPERS_H_INCLUDED
namespace soci
{
// These wrapper types can be used by the application
// with 'into' and 'use' elements in order to guide the library
// in selecting specialized methods for binding and transferring data;
// if the target database does not provide any such specialized methods,
// it is expected to handle these wrappers as equivalent
// to their contained field types.
struct xml_type
{
std::string value;
};
struct long_string
{
std::string value;
};
} // namespace soci
#endif // SOCI_TYPE_WRAPPERS_H_INCLUDED
......@@ -143,6 +143,34 @@ void oracle_standard_into_type_backend::define_by_pos(
data = &bbe->lobp_;
}
break;
case x_xmltype:
case x_longstring:
{
oracleType = SQLT_CLOB;
OCILobLocator * lobp;
sword res = OCIDescriptorAlloc(statement_.session_.envhp_,
reinterpret_cast<dvoid**>(&lobp), OCI_DTYPE_LOB, 0, 0);
if (res != OCI_SUCCESS)
{
throw_oracle_soci_error(res, statement_.session_.errhp_);
}
res = OCILobCreateTemporary(statement_.session_.svchp_,
statement_.session_.errhp_,
lobp, 0, SQLCS_IMPLICIT,
OCI_TEMP_CLOB, OCI_ATTR_NOCACHE, OCI_DURATION_SESSION);
if (res != OCI_SUCCESS)
{
throw_oracle_soci_error(res, statement_.session_.errhp_);
}
size = sizeof(lobp);
data = &ociData_;
ociData_ = lobp;
}
break;
}
sword res = OCIDefineByPos(statement_.stmtp_, &defnp_,
......@@ -167,6 +195,32 @@ void oracle_standard_into_type_backend::pre_fetch()
}
}
void oracle_standard_into_type_backend::read_from_lob(OCILobLocator * lobp, std::string & value)
{
ub4 len;
sword res = OCILobGetLength(statement_.session_.svchp_, statement_.session_.errhp_,
lobp, &len);
if (res != OCI_SUCCESS)
{
throw_oracle_soci_error(res, statement_.session_.errhp_);
}
std::vector<char> buf(len);
ub4 offset = 1;
res = OCILobRead(statement_.session_.svchp_, statement_.session_.errhp_,
lobp, &len,
offset, reinterpret_cast<dvoid*>(&buf[0]),
len, 0, 0, 0, 0);
if (res != OCI_SUCCESS)
{
throw_oracle_soci_error(res, statement_.session_.errhp_);
}
value.assign(buf.begin(), buf.end());
}
void oracle_standard_into_type_backend::post_fetch(
bool gotData, bool calledFromFetch, indicator *ind)
{
......@@ -218,6 +272,26 @@ void oracle_standard_into_type_backend::post_fetch(
statement *st = static_cast<statement *>(data_);
st->define_and_bind();
}
else if (type_ == x_xmltype)
{
if (indOCIHolder_ != -1)
{
OCILobLocator * lobp = static_cast<OCILobLocator *>(ociData_);
xml_type * xml = static_cast<xml_type *>(data_);
read_from_lob(lobp, xml->value);
}
}
else if (type_ == x_longstring)
{
if (indOCIHolder_ != -1)
{
OCILobLocator * lobp = static_cast<OCILobLocator *>(ociData_);
long_string * ls = static_cast<long_string *>(data_);
read_from_lob(lobp, ls->value);
}
}
}
// then - deal with indicators
......@@ -257,6 +331,15 @@ void oracle_standard_into_type_backend::post_fetch(
void oracle_standard_into_type_backend::clean_up()
{
if (type_ == x_xmltype || type_ == x_longstring)
{
OCILobLocator * lobp = static_cast<OCILobLocator *>(ociData_);
// ignore errors from this call
(void) OCILobFreeTemporary(statement_.session_.svchp_, statement_.session_.errhp_,
lobp);
}
if (defnp_ != NULL)
{
OCIHandleFree(defnp_, OCI_HTYPE_DEFINE);
......
......@@ -11,6 +11,7 @@
#include "error.h"
#include "soci/rowid.h"
#include "soci/statement.h"
#include "soci/type-wrappers.h"
#include "soci/soci-platform.h"
#include "soci-compiler.h"
......@@ -140,6 +141,34 @@ void oracle_standard_use_type_backend::prepare_for_bind(
data = &bbe->lobp_;
}
break;
case x_xmltype:
case x_longstring:
{
oracleType = SQLT_CLOB;
OCILobLocator * lobp;
sword res = OCIDescriptorAlloc(statement_.session_.envhp_,
reinterpret_cast<dvoid**>(&lobp), OCI_DTYPE_LOB, 0, 0);
if (res != OCI_SUCCESS)
{
throw_oracle_soci_error(res, statement_.session_.errhp_);
}
res = OCILobCreateTemporary(statement_.session_.svchp_,
statement_.session_.errhp_,
lobp, 0, SQLCS_IMPLICIT,
OCI_TEMP_CLOB, OCI_ATTR_NOCACHE, OCI_DURATION_SESSION);
if (res != OCI_SUCCESS)
{
throw_oracle_soci_error(res, statement_.session_.errhp_);
}
size = sizeof(lobp);
data = &ociData_;
ociData_ = lobp;
}
break;
}
}
......@@ -203,6 +232,40 @@ void oracle_standard_use_type_backend::bind_by_name(
statement_.boundByName_ = true;
}
void oracle_standard_use_type_backend::write_to_lob(OCILobLocator * lobp, const std::string & value)
{
ub4 toWrite = value.size();
ub4 offset = 1;
sword res = OCILobWrite(statement_.session_.svchp_, statement_.session_.errhp_,
lobp, &toWrite, offset,
reinterpret_cast<dvoid*>(const_cast<char*>(value.data())),
toWrite, OCI_ONE_PIECE, 0, 0, 0, SQLCS_IMPLICIT);
if (res != OCI_SUCCESS)
{
throw_oracle_soci_error(res, statement_.session_.errhp_);
}
ub4 len;
res = OCILobGetLength(statement_.session_.svchp_, statement_.session_.errhp_,
lobp, &len);
if (res != OCI_SUCCESS)
{
throw_oracle_soci_error(res, statement_.session_.errhp_);
}
if (toWrite < len)
{
res = OCILobTrim(statement_.session_.svchp_, statement_.session_.errhp_,
lobp, toWrite);
if (res != OCI_SUCCESS)
{
throw_oracle_soci_error(res, statement_.session_.errhp_);
}
}
}
void oracle_standard_use_type_backend::pre_use(indicator const *ind)
{
// first deal with data
......@@ -278,6 +341,24 @@ void oracle_standard_use_type_backend::pre_use(indicator const *ind)
s->undefine_and_bind();
}
break;
case x_xmltype:
{
OCILobLocator * lobp = static_cast<OCILobLocator *>(ociData_);
xml_type * xml = static_cast<xml_type *>(data_);
write_to_lob(lobp, xml->value);
}
break;
case x_longstring:
{
OCILobLocator * lobp = static_cast<OCILobLocator *>(ociData_);
long_string * ls = static_cast<long_string *>(data_);
write_to_lob(lobp, ls->value);
}
break;
case x_rowid:
case x_blob:
// nothing to do
......@@ -444,6 +525,8 @@ void oracle_standard_use_type_backend::post_use(bool gotData, indicator *ind)
break;
case x_rowid:
case x_blob:
case x_xmltype:
case x_longstring:
// nothing to do here
break;
}
......@@ -471,6 +554,15 @@ void oracle_standard_use_type_backend::post_use(bool gotData, indicator *ind)
void oracle_standard_use_type_backend::clean_up()
{
if (type_ == x_xmltype || type_ == x_longstring)
{
OCILobLocator * lobp = static_cast<OCILobLocator *>(ociData_);
// ignore errors from this call
(void) OCILobFreeTemporary(statement_.session_.svchp_, statement_.session_.errhp_,
lobp);
}
if (bindp_ != NULL)
{
OCIHandleFree(bindp_, OCI_HTYPE_DEFINE);
......
......@@ -158,9 +158,11 @@ void oracle_vector_into_type_backend::define_by_pos(
}
break;
case x_statement: break; // not supported
case x_rowid: break; // not supported
case x_blob: break; // not supported
case x_xmltype: break; // not supported
case x_longstring: break; // not supported
case x_statement: break; // not supported
case x_rowid: break; // not supported
case x_blob: break; // not supported
}
sword res = OCIDefineByPos(statement_.stmtp_, &defnp_,
......@@ -380,9 +382,11 @@ void oracle_vector_into_type_backend::resize(std::size_t sz)
}
break;
case x_statement: break; // not supported
case x_rowid: break; // not supported
case x_blob: break; // not supported
case x_xmltype: break; // not supported
case x_longstring: break; // not supported
case x_statement: break; // not supported
case x_rowid: break; // not supported
case x_blob: break; // not supported
}
end_var_ = sz;
......@@ -473,9 +477,11 @@ std::size_t oracle_vector_into_type_backend::full_size()
}
break;
case x_statement: break; // not supported
case x_rowid: break; // not supported
case x_blob: break; // not supported
case x_xmltype: break; // not supported
case x_longstring: break; // not supported
case x_statement: break; // not supported
case x_rowid: break; // not supported
case x_blob: break; // not supported
}
return sz;
......
......@@ -155,9 +155,11 @@ void oracle_vector_use_type_backend::prepare_for_bind(
}
break;
case x_statement: break; // not supported
case x_rowid: break; // not supported
case x_blob: break; // not supported
case x_xmltype: break; // not supported
case x_longstring: break; // not supported
case x_statement: break; // not supported
case x_rowid: break; // not supported
case x_blob: break; // not supported
}
}
......@@ -398,9 +400,11 @@ std::size_t oracle_vector_use_type_backend::full_size()
}
break;
case x_statement: break; // not supported
case x_rowid: break; // not supported
case x_blob: break; // not supported
case x_xmltype: break; // not supported
case x_longstring: break; // not supported
case x_statement: break; // not supported
case x_rowid: break; // not supported
case x_blob: break; // not supported
}
return sz;
......
......@@ -12,6 +12,7 @@
#include "common.h"
#include "soci/rowid.h"
#include "soci/blob.h"
#include "soci/type-wrappers.h"
#include "soci-exchange-cast.h"
#include <libpq/libpq-fs.h> // libpq
#include <cctype>
......@@ -150,6 +151,14 @@ void postgresql_standard_into_type_backend::post_fetch(
bbe->oid_ = oid;
}
break;
case x_xmltype:
{
long_string * ls = static_cast<long_string *>(data_);
std::string * dest = &(ls->value);
dest->assign(buf);
}
break;
default:
throw soci_error("Into element used with non-supported type.");
......
......@@ -9,6 +9,7 @@
#include "soci/postgresql/soci-postgresql.h"
#include "soci/blob.h"
#include "soci/rowid.h"
#include "soci/type-wrappers.h"
#include "soci/soci-platform.h"
#include "soci-dtocstr.h"
#include "soci-exchange-cast.h"
......@@ -159,7 +160,25 @@ void postgresql_standard_use_type_backend::pre_use(indicator const * ind)
snprintf(buf_, bufSize, "%lu", bbe->oid_);
}
break;
case x_xmltype:
{
xml_type * xml = static_cast<xml_type *>(data_);
std::string * s = &(xml->value);
buf_ = new char[s->size() + 1];
std::strcpy(buf_, s->c_str());
}
break;
case x_longstring:
{
long_string * ls = static_cast<long_string *>(data_);
std::string * s = &(ls->value);
buf_ = new char[s->size() + 1];
std::strcpy(buf_, s->c_str());
}
break;
default:
throw soci_error("Use element used with non-supported type.");
}
......
......@@ -10,6 +10,7 @@
#include "soci/postgresql/soci-postgresql.h"
#include "soci-cstrtod.h"
#include "common.h"
#include "soci/type-wrappers.h"
#include <libpq/libpq-fs.h> // libpq
#include <cctype>
#include <cstdio>
......@@ -59,6 +60,16 @@ void set_invector_(void * p, int indx, T const & val)
v[indx] = val;
}
template <typename T, typename V>
void set_invector_wrappers_(void * p, int indx, V const & val)
{
std::vector<T> * dest =
static_cast<std::vector<T> *>(p);
std::vector<T> & v = *dest;
v[indx].value = val;
}
} // namespace anonymous
void postgresql_vector_into_type_backend::post_fetch(bool gotData, indicator * ind)
......@@ -149,6 +160,12 @@ void postgresql_vector_into_type_backend::post_fetch(bool gotData, indicator * i
set_invector_(data_, i, t);
}
break;
case x_xmltype:
set_invector_wrappers_<xml_type, std::string>(data_, i, buf);
break;
case x_longstring:
set_invector_wrappers_<long_string, std::string>(data_, i, buf);
break;
default:
throw soci_error("Into element used with non-supported type.");
......@@ -208,6 +225,12 @@ void postgresql_vector_into_type_backend::resize(std::size_t sz)
case x_stdtm:
resizevector_<std::tm>(data_, sz);
break;
case x_xmltype:
resizevector_<xml_type>(data_, sz);
break;
case x_longstring:
resizevector_<long_string>(data_, sz);
break;
default:
throw soci_error("Into vector element used with non-supported type.");
}
......@@ -270,6 +293,12 @@ std::size_t postgresql_vector_into_type_backend::full_size()
case x_stdtm:
sz = get_vector_size<std::tm>(data_);
break;
case x_xmltype:
sz = get_vector_size<xml_type>(data_);
break;
case x_longstring:
sz = get_vector_size<long_string>(data_);
break;
default:
throw soci_error("Into vector element used with non-supported type.");
}
......
......@@ -10,6 +10,7 @@
#include "soci/postgresql/soci-postgresql.h"
#include "soci-dtocstr.h"
#include "common.h"
#include "soci/type-wrappers.h"
#include <libpq/libpq-fs.h> // libpq
#include <cctype>
#include <cstdio>
......@@ -178,6 +179,26 @@ void postgresql_vector_use_type_backend::pre_use(indicator const * ind)
v[i].tm_hour, v[i].tm_min, v[i].tm_sec);
}
break;
case x_xmltype:
{
std::vector<xml_type> * pv
= static_cast<std::vector<xml_type> *>(data_);
std::vector<xml_type> & v = *pv;
buf = new char[v[i].value.size() + 1];
std::strcpy(buf, v[i].value.c_str());
}
break;
case x_longstring:
{
std::vector<long_string> * pv
= static_cast<std::vector<long_string> *>(data_);
std::vector<long_string> & v = *pv;
buf = new char[v[i].value.size() + 1];
std::strcpy(buf, v[i].value.c_str());
}
break;
default:
throw soci_error(
......@@ -251,6 +272,12 @@ std::size_t postgresql_vector_use_type_backend::full_size()
case x_stdtm:
sz = get_vector_size<std::tm>(data_);
break;
case x_xmltype:
sz = get_vector_size<xml_type>(data_);
break;
case x_longstring:
sz = get_vector_size<long_string>(data_);
break;
default:
throw soci_error("Use vector element used with non-supported type.");
}
......
......@@ -582,6 +582,8 @@ bool name_exists_check_failed(statement_wrapper & wrapper,
iterator const it = wrapper.use_blob.find(name);