summaryrefslogtreecommitdiff
path: root/odb/odb/relational/oracle/context.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'odb/odb/relational/oracle/context.cxx')
-rw-r--r--odb/odb/relational/oracle/context.cxx795
1 files changed, 795 insertions, 0 deletions
diff --git a/odb/odb/relational/oracle/context.cxx b/odb/odb/relational/oracle/context.cxx
new file mode 100644
index 0000000..12ce0aa
--- /dev/null
+++ b/odb/odb/relational/oracle/context.cxx
@@ -0,0 +1,795 @@
+// file : odb/relational/oracle/context.cxx
+// license : GNU GPL v3; see accompanying LICENSE file
+
+#include <cassert>
+#include <sstream>
+
+#include <odb/sql-token.hxx>
+#include <odb/sql-lexer.hxx>
+
+#include <odb/relational/oracle/context.hxx>
+
+using namespace std;
+
+namespace relational
+{
+ namespace oracle
+ {
+ namespace
+ {
+ struct type_map_entry
+ {
+ char const* const cxx_type;
+ char const* const db_type;
+ char const* const db_id_type;
+ bool const null;
+ };
+
+ type_map_entry type_map[] =
+ {
+ {"bool", "NUMBER(1)", 0, false},
+
+ {"char", "CHAR(1)", 0, false},
+ {"signed char", "NUMBER(3)", 0, false},
+ {"unsigned char", "NUMBER(3)", 0, false},
+
+ {"short int", "NUMBER(5)", 0, false},
+ {"short unsigned int", "NUMBER(5)", 0, false},
+
+ {"int", "NUMBER(10)", 0, false},
+ {"unsigned int", "NUMBER(10)", 0, false},
+
+ {"long int", "NUMBER(19)", 0, false},
+ {"long unsigned int", "NUMBER(20)", 0, false},
+
+ {"long long int", "NUMBER(19)", 0, false},
+ {"long long unsigned int", "NUMBER(20)", 0, false},
+
+ {"float", "BINARY_FLOAT", 0, false},
+ {"double", "BINARY_DOUBLE", 0, false},
+
+ // Oracle treats empty VARCHAR2 (and NVARCHAR2) strings as NULL.
+ //
+ {"::std::string", "VARCHAR2(512)", 0, true},
+
+ {"::size_t", "NUMBER(20)", 0, false},
+ {"::std::size_t", "NUMBER(20)", 0, false}
+ };
+ }
+
+ context* context::current_;
+
+ context::
+ ~context ()
+ {
+ if (current_ == this)
+ current_ = 0;
+ }
+
+ context::
+ context (ostream& os,
+ semantics::unit& u,
+ options_type const& ops,
+ features_type& f,
+ sema_rel::model* m)
+ : root_context (os, u, ops, f, data_ptr (new (shared) data (os))),
+ base_context (static_cast<data*> (root_context::data_.get ()), m),
+ data_ (static_cast<data*> (base_context::data_))
+ {
+ assert (current_ == 0);
+ current_ = this;
+
+ generate_grow = false;
+ need_alias_as = false;
+ insert_send_auto_id = false;
+ delay_freeing_statement_result = false;
+ need_image_clone = true;
+ generate_bulk = true;
+ global_index = true;
+ global_fkey = true;
+ data_->bind_vector_ = "oracle::bind*";
+
+ // Populate the C++ type to DB type map.
+ //
+ for (size_t i (0); i < sizeof (type_map) / sizeof (type_map_entry); ++i)
+ {
+ type_map_entry const& e (type_map[i]);
+
+ type_map_type::value_type v (
+ e.cxx_type,
+ db_type_type (
+ e.db_type, e.db_id_type ? e.db_id_type : e.db_type, e.null));
+
+ data_->type_map_.insert (v);
+ }
+ }
+
+ context::
+ context ()
+ : data_ (current ().data_)
+ {
+ }
+
+ string const& context::
+ convert_expr (string const& sqlt, semantics::data_member& m, bool to)
+ {
+ sql_type const& t (parse_sql_type (sqlt, m));
+ return to ? t.to : t.from;
+ }
+
+ string context::
+ quote_id_impl (qname const& id) const
+ {
+ string r;
+
+ bool f (true);
+ for (qname::iterator i (id.begin ()); i < id.end (); ++i)
+ {
+ if (i->empty ())
+ continue;
+
+ if (f)
+ f = false;
+ else
+ r += '.';
+
+ r += '"';
+ r.append (*i, 0, 30); // Max identifier length is 30.
+ r += '"';
+ }
+
+ return r;
+ }
+
+ string context::
+ database_type_impl (semantics::type& t,
+ semantics::names* hint,
+ bool id,
+ bool* null)
+ {
+ string r (base_context::database_type_impl (t, hint, id, null));
+
+ if (!r.empty ())
+ return r;
+
+ using semantics::array;
+
+ // char[N] mapping.
+ //
+ if (array* a = dynamic_cast<array*> (&t))
+ {
+ semantics::type& bt (a->base_type ());
+ if (bt.is_a<semantics::fund_char> ())
+ {
+ unsigned long long n (a->size ());
+
+ if (n == 0)
+ return r;
+ else if (n == 1)
+ r = "CHAR";
+ else
+ {
+ r = "VARCHAR2";
+ n--;
+ }
+
+ // Oracle VARCHAR2 limit is 4000 bytes. Since there are no good
+ // alternatives (CLOB?), let the user specify the mapping.
+ //
+ if (n > 4000)
+ return "";
+
+ // Allow empty VARCHAR2 values.
+ //
+ if (null != 0 && r == "VARCHAR2")
+ *null = true;
+
+ ostringstream ostr;
+ ostr << n;
+ r += '(';
+ r += ostr.str ();
+ r += ')';
+ }
+ }
+
+ return r;
+ }
+
+ bool context::
+ unsigned_integer (semantics::type& t)
+ {
+ semantics::type* wt (wrapper (t));
+ const string& s ((wt == 0 ? t : utype (*wt)).name ());
+
+ return s == "bool" ||
+ s == "unsigned char" ||
+ s == "short unsigned int" ||
+ s == "unsigned int" ||
+ s == "long unsigned int" ||
+ s == "long long unsigned int";
+ }
+
+ qname context::
+ sequence_name (qname const& table)
+ {
+ string n;
+
+ if (options.sequence_suffix ().count (db) != 0)
+ n = table.uname () + options.sequence_suffix ()[db];
+ else
+ n = compose_name (table.uname (), "seq");
+
+ n = transform_name (n, sql_name_sequence);
+
+ qname r (table.qualifier ());
+ r.append (n);
+ return r;
+ }
+
+ //
+ // SQL type parsing.
+ //
+
+ sql_type const& context::
+ parse_sql_type (string const& t, semantics::data_member& m, bool custom)
+ {
+ // If this proves to be too expensive, we can maintain a cache of
+ // parsed types across contexts.
+ //
+ data::sql_type_cache::iterator i (data_->sql_type_cache_.find (t));
+
+ if (i != data_->sql_type_cache_.end ()
+ && (custom ? i->second.custom_cached : i->second.straight_cached))
+ {
+ return (custom ? i->second.custom : i->second.straight);
+ }
+ else
+ {
+ try
+ {
+ sql_type st (
+ parse_sql_type (
+ t,
+ custom ? &unit.get<custom_db_types> ("custom-db-types") : 0));
+
+ if (custom)
+ return data_->sql_type_cache_[t].cache_custom (st);
+ else
+ return data_->sql_type_cache_[t].cache_straight (st);
+ }
+ catch (invalid_sql_type const& e)
+ {
+ cerr << m.file () << ":" << m.line () << ":" << m.column ()
+ << ": error: " << e.message () << endl;
+
+ throw operation_failed ();
+ }
+ }
+ }
+
+ inline sql_type
+ error (bool fail, string const& m)
+ {
+ if (!fail)
+ return sql_type ();
+ else
+ throw context::invalid_sql_type (m);
+ }
+
+ sql_type context::
+ parse_sql_type (string sqlt, custom_db_types const* ct)
+ {
+ try
+ {
+ sql_type r;
+
+ // First run the type through the custom mapping, if requested.
+ //
+ if (ct != 0)
+ {
+ for (custom_db_types::const_iterator i (ct->begin ());
+ i != ct->end (); ++i)
+ {
+ custom_db_type const& t (*i);
+
+ if (t.type.match (sqlt))
+ {
+ r.to = t.type.replace (sqlt, t.to);
+ r.from = t.type.replace (sqlt, t.from);
+ sqlt = t.type.replace (sqlt, t.as);
+ break;
+ }
+ }
+ }
+
+ sql_lexer l (sqlt);
+
+ // While most type names use single identifier, there are
+ // a couple of exceptions to this rule:
+ //
+ // CHARACTER VARYING (VARCHAR2)
+ // CHAR VARYING (VARCHAR2)
+ // NATIONAL CHARACTER (NCHAR)
+ // NATIONAL CHAR (NCHAR)
+ // NCHAR VARYING (NVARCHAR2)
+ // NATIONAL CHARACTER VARYING (NVARCHAR2)
+ // NATIONAL CHAR VARYING (NVARCHAR2)
+ // NCHAR VARYING (NVARCHAR2)
+ // DOUBLE PRECISION (FLOAT(126))
+ // INTERVAL YEAR TO MONTH
+ // INTERVAL DAY TO SECOND
+ //
+ enum state
+ {
+ parse_identifier,
+ parse_prec,
+ parse_done
+ };
+
+ state s (parse_identifier);
+ string prefix;
+ sql_token t (l.next ());
+
+ while (t.type () != sql_token::t_eos)
+ {
+ sql_token::token_type tt (t.type ());
+
+ switch (s)
+ {
+ case parse_identifier:
+ {
+ if (tt == sql_token::t_identifier)
+ {
+ string const& id (context::upcase (t.identifier ()));
+
+ //
+ // Numeric types.
+ //
+ if ((id == "NUMBER") && prefix.empty ())
+ {
+ // If NUMBER has no precision/scale, then it is a floating-
+ // point number. We indicate this by having no precision.
+ //
+ r.type = sql_type::NUMBER;
+ s = parse_prec;
+ }
+ else if ((id == "DEC" || id == "DECIMAL" || id == "NUMERIC")
+ && prefix.empty ())
+ {
+ // DEC, DECIMAL, and NUMERIC are equivalent to NUMBER in
+ // all ways except that they may not represent a floating
+ // point number. The scale defaults to zero.
+ //
+ r.type = sql_type::NUMBER;
+ s = parse_prec;
+ }
+ else if ((id == "INT" || id == "INTEGER" || id == "SMALLINT")
+ && prefix.empty ())
+ {
+ // INT, INTEGER, and SMALLINT map to NUMBER(38). They may not
+ // have precision or scale explicitly specified.
+ //
+ r.type = sql_type::NUMBER;
+ r.prec = true;
+ r.prec_value = 38;
+
+ s = parse_done;
+ }
+ //
+ // Floating point types.
+ //
+ else if (id == "FLOAT" && prefix.empty ())
+ {
+ r.type = sql_type::FLOAT;
+ r.prec = true;
+ r.prec_value = 126;
+
+ s = parse_prec;
+ }
+ else if (id == "DOUBLE" && prefix.empty ())
+ {
+ prefix = id;
+ }
+ else if (id == "PRECISION" && prefix == "DOUBLE")
+ {
+ r.type = sql_type::FLOAT;
+ r.prec = true;
+ r.prec_value = 126;
+
+ s = parse_done;
+ }
+ else if (id == "REAL" && prefix.empty ())
+ {
+ r.type = sql_type::FLOAT;
+ r.prec = true;
+ r.prec_value = 63;
+
+ s = parse_done;
+ }
+ else if (id == "BINARY_FLOAT" && prefix.empty ())
+ {
+ r.type = sql_type::BINARY_FLOAT;
+ s = parse_done;
+ }
+ else if (id == "BINARY_DOUBLE" && prefix.empty ())
+ {
+ r.type = sql_type::BINARY_DOUBLE;
+ s = parse_done;
+ }
+ //
+ // Date-time types.
+ //
+ else if (id == "DATE" && prefix.empty ())
+ {
+ r.type = sql_type::DATE;
+ s = parse_done;
+ }
+ else if (id == "TIMESTAMP" && prefix.empty ())
+ {
+ prefix = id;
+ }
+ else if (id == "INTERVAL" && prefix.empty ())
+ {
+ prefix = id;
+ }
+ else if (id == "YEAR" && prefix == "INTERVAL")
+ {
+ prefix += " ";
+ prefix += id;
+
+ r.prec = true;
+ r.prec_value = 2;
+ s = parse_prec;
+ }
+ else if (id == "DAY" && prefix == "INTERVAL")
+ {
+ prefix += " ";
+ prefix += id;
+
+ r.prec = true;
+ r.prec_value = 2;
+ s = parse_prec;
+ }
+ else if (id == "TO" &&
+ (prefix == "INTERVAL YEAR" ||
+ prefix == "INTERVAL DAY"))
+ {
+ prefix += " ";
+ prefix += id;
+ }
+ else if (id == "MONTH" && prefix == "INTERVAL YEAR TO")
+ {
+ r.type = sql_type::INTERVAL_YM;
+ s = parse_done;
+ }
+ else if (id == "SECOND" && prefix == "INTERVAL DAY TO")
+ {
+ r.type = sql_type::INTERVAL_DS;
+
+ // Store seconds precision in scale since prec holds
+ // the days precision.
+ //
+ r.scale = true;
+ r.scale_value = 6;
+ s = parse_prec;
+ }
+ //
+ // Timestamp with time zone (not supported).
+ //
+ else if (id == "WITH" && prefix == "TIMESTAMP")
+ {
+ prefix += " ";
+ prefix += id;
+ }
+ else if (id == "TIME" &&
+ (prefix == "TIMESTAMP WITH" ||
+ prefix == "TIMESTAMP WITH LOCAL"))
+ {
+ prefix += " ";
+ prefix += id;
+ }
+ else if (id == "LOCAL" && prefix == "TIMESTAMP WITH")
+ {
+ prefix += " ";
+ prefix += id;
+ }
+ else if (id == "ZONE" &&
+ (prefix == "TIMESTAMP WITH LOCAL TIME" ||
+ prefix == "TIMESTAMP WITH TIME"))
+ {
+ return error (ct, "Oracle timestamps with time zones are "
+ "not currently supported");
+ }
+ //
+ // String and binary types.
+ //
+ else if (id == "CHAR")
+ {
+ prefix += prefix.empty () ? "" : " ";
+ prefix += id;
+ }
+ else if (id == "CHARACTER")
+ {
+ prefix += prefix.empty () ? "" : " ";
+ prefix += id;
+ }
+ else if (id == "NCHAR")
+ {
+ prefix += prefix.empty () ? "" : " ";
+ prefix += id;
+ }
+ else if (id == "VARCHAR" || id == "VARCHAR2")
+ {
+ // VARCHAR is currently mapped to VARCHAR2 in Oracle server.
+ // However, this may change in future versions.
+ //
+ r.type = sql_type::VARCHAR2;
+ r.byte_semantics = true;
+ s = parse_prec;
+ }
+ else if (id == "NVARCHAR2")
+ {
+ r.type = sql_type::NVARCHAR2;
+ r.byte_semantics = false;
+ s = parse_prec;
+ }
+ else if (id == "VARYING")
+ {
+ // VARYING always appears at the end of an identifier.
+ //
+ if (prefix == "CHAR" || prefix == "CHARACTER")
+ {
+ r.type = sql_type::VARCHAR2;
+ r.byte_semantics = true;
+ }
+ else if (prefix == "NCHAR" ||
+ prefix == "NATIONAL CHAR" ||
+ prefix == "NATIONAL CHARACTER")
+ {
+ r.type = sql_type::NVARCHAR2;
+ r.byte_semantics = false;
+ }
+
+ s = parse_prec;
+ }
+ else if (id == "NATIONAL" && prefix.empty ())
+ {
+ prefix = id;
+ }
+ else if (id == "RAW" && prefix.empty ())
+ {
+ r.type = sql_type::RAW;
+ s = parse_prec;
+ }
+ //
+ // LOB types.
+ //
+ else if (id == "BLOB" && prefix.empty ())
+ {
+ r.type = sql_type::BLOB;
+ s = parse_done;
+ }
+ else if (id == "CLOB" && prefix.empty ())
+ {
+ r.type = sql_type::CLOB;
+ s = parse_done;
+ }
+ else if (id == "NCLOB" && prefix.empty ())
+ {
+ r.type = sql_type::NCLOB;
+ s = parse_done;
+ }
+ //
+ // LONG types.
+ //
+ else if (id == "LONG")
+ return error (ct, "Oracle LONG types are not supported");
+ else
+ return error (ct, "unknown Oracle type '" +
+ t.identifier () + "'");
+
+ t = l.next ();
+ continue;
+ }
+ else if (!prefix.empty ())
+ {
+ // Some prefixes can also be type names if not followed
+ // by the actual type name.
+ //
+
+ if (prefix == "CHAR" || prefix == "CHARACTER")
+ {
+ r.type = sql_type::CHAR;
+ r.byte_semantics = true;
+ r.prec = true;
+ r.prec_value = 1;
+ }
+ else if (prefix == "NCHAR" ||
+ prefix == "NATIONAL CHAR" ||
+ prefix == "NATIONAL CHARACTER")
+ {
+ r.type = sql_type::NCHAR;
+ r.byte_semantics = false;
+ r.prec = true;
+ r.prec_value = 1;
+ }
+ else if (prefix == "TIMESTAMP")
+ {
+ r.type = sql_type::TIMESTAMP;
+ r.prec = true;
+ r.prec_value = 6;
+ }
+ else
+ return error (ct, "incomplete Oracle type declaration: '" +
+ prefix + "'");
+
+ // All of the possible types handled in this block can take
+ // an optional precision specifier. Set the state and fall
+ // through to the parse_prec handler.
+ //
+ s = parse_prec;
+ }
+ else
+ {
+ assert (r.type == sql_type::invalid);
+ return error (ct, "unexepected '" + t.literal () +
+ "' in Oracle type declaration");
+ }
+ }
+ // Fall through.
+ case parse_prec:
+ {
+ if (t.punctuation () == sql_token::p_lparen)
+ {
+ t = l.next ();
+
+ if (t.type () != sql_token::t_int_lit)
+ {
+ return error (ct, "integer size/precision expected in "
+ "Oracle type declaration");
+ }
+
+ // Parse the precision.
+ //
+ {
+ unsigned short v;
+ istringstream is (t.literal ());
+
+ if (!(is >> v && is.eof ()))
+ {
+ return error (ct, "invalid prec value '" + t.literal () +
+ "' in Oracle type declaration");
+ }
+
+ // Store seconds precision in scale since prec holds
+ // the days precision for INTERVAL DAY TO SECOND.
+ //
+ if (r.type == sql_type::INTERVAL_DS)
+ {
+ r.scale = true;
+ r.scale_value = static_cast<short> (v);
+ }
+ else
+ {
+ r.prec = true;
+ r.prec_value = v;
+ }
+
+ t = l.next ();
+ }
+
+ // Parse the scale if present.
+ //
+ if (t.punctuation () == sql_token::p_comma)
+ {
+ // Scale can only be specified for NUMBER.
+ //
+ if (r.type != sql_type::NUMBER)
+ {
+ return error (ct, "invalid scale in Oracle type "
+ "declaration");
+ }
+
+ t = l.next ();
+
+ if (t.type () != sql_token::t_int_lit)
+ {
+ return error (ct, "integer scale expected in Oracle type "
+ "declaration");
+ }
+
+ short v;
+ istringstream is (t.literal ());
+
+ if (!(is >> v && is.eof ()))
+ {
+ return error (ct, "invalid scale value '" + t.literal () +
+ "' in Oracle type declaration");
+ }
+
+ r.scale = true;
+ r.scale_value = v;
+
+ t = l.next ();
+ }
+ else if (t.type () == sql_token::t_identifier)
+ {
+ const string& id (context::upcase (t.identifier ()));
+
+ if (id == "CHAR")
+ r.byte_semantics = false;
+ else if (id != "BYTE")
+ {
+ return error (ct, "invalid keyword '" + t.literal () +
+ "' in Oracle type declaration");
+ }
+
+ t = l.next ();
+ }
+
+ if (t.punctuation () != sql_token::p_rparen)
+ {
+ return error (ct, "expected ')' in Oracle type declaration");
+ }
+ else
+ t = l.next ();
+ }
+
+ s = r.type == sql_type::invalid ? parse_identifier : parse_done;
+ continue;
+ }
+ case parse_done:
+ {
+ return error (ct, "unexepected '" + t.literal () + "' in Oracle "
+ "type declaration");
+ break;
+ }
+ }
+ }
+
+ // Some prefixes can also be type names if not followed by the actual
+ // type name.
+ //
+ if (r.type == sql_type::invalid)
+ {
+ if (!prefix.empty ())
+ {
+ if (prefix == "CHAR" || prefix == "CHARACTER")
+ {
+ r.type = sql_type::CHAR;
+ r.byte_semantics = true;
+ r.prec = true;
+ r.prec_value = 1;
+ }
+ else if (prefix == "NCHAR" ||
+ prefix == "NATIONAL CHAR" ||
+ prefix == "NATIONAL CHARACTER")
+ {
+ r.type = sql_type::NCHAR;
+ r.byte_semantics = false;
+ r.prec = true;
+ r.prec_value = 1;
+ }
+ else if (prefix == "TIMESTAMP")
+ {
+ r.type = sql_type::TIMESTAMP;
+ r.prec = true;
+ r.prec_value = 6;
+ }
+ else
+ return error (ct, "incomplete Oracle type declaration: '" +
+ prefix + "'");
+ }
+ else
+ return error (ct, "invalid Oracle type declaration");
+ }
+
+ return r;
+ }
+ catch (sql_lexer::invalid_input const& e)
+ {
+ return error (ct, "invalid Oracle type declaration: " + e.message);
+ }
+ }
+ }
+}