From 36863f5f31c202e45c8a406a321deb8d237cbc14 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 22 Jan 2015 14:37:10 +0200 Subject: Add support for warning about SQL name truncations in Oracle Also detect and issue diagnostics when such truncations lead to name conflicts. --- NEWS | 5 ++ odb/context.cxx | 2 + odb/context.hxx | 7 +- odb/options.cli | 9 ++ odb/relational/model.cxx | 9 +- odb/relational/model.hxx | 4 +- odb/relational/oracle/schema.cxx | 173 ++++++++++++++++++++++++++++++++++++++- 7 files changed, 197 insertions(+), 12 deletions(-) diff --git a/NEWS b/NEWS index 85eadc6..5ff9359 100644 --- a/NEWS +++ b/NEWS @@ -36,6 +36,11 @@ Version 2.4.0 * Support for calling MySQL stored procedures. For details and limitations refer to Section 17.7, "MySQL Stored Procedures" in the ODB manual. + * New option, --oracle-warn-truncation, makes ODB warn about SQL names + that are longer than 30 characters and are therefore truncated. ODB + now also detects when such truncations lead to name conflicts and + issues diagnostics even without this option specified. + Version 2.3.0 * Support for database schema evolution, including schema migration, data diff --git a/odb/context.cxx b/odb/context.cxx index dfd75f6..4af13b7 100644 --- a/odb/context.cxx +++ b/odb/context.cxx @@ -617,6 +617,7 @@ context (ostream& os_, features_type& f, data_ptr d) : data_ (d ? d : data_ptr (new (shared) data (os_))), + extra (data_->extra_), os (data_->os_), unit (u), options (ops), @@ -727,6 +728,7 @@ context (ostream& os_, context:: context () : data_ (current ().data_), + extra (current ().extra), os (current ().os), unit (current ().unit), options (current ().options), diff --git a/odb/context.hxx b/odb/context.hxx index 0af3b3c..805691f 100644 --- a/odb/context.hxx +++ b/odb/context.hxx @@ -1512,7 +1512,8 @@ protected: ~data () {} data (std::ostream& os) - : os_ (os.rdbuf ()), + : extra_ (0), + os_ (os.rdbuf ()), in_comment_ (false), top_object_ (0), cur_object_ (0), @@ -1522,6 +1523,8 @@ protected: } public: + void* extra_; + std::ostream os_; std::stack os_stack_; @@ -1551,6 +1554,8 @@ protected: public: typedef ::features features_type; + void*& extra; // Extra data that may need to be shared by a sub-system. + std::ostream& os; semantics::unit& unit; options_type const& options; diff --git a/odb/options.cli b/odb/options.cli index 6d67ca7..350bbfc 100644 --- a/odb/options.cli +++ b/odb/options.cli @@ -1027,6 +1027,15 @@ class options \cb{10.1} or later is assumed." }; + bool --oracle-warn-truncation + { + "Warn about SQL names that are longer than 30 characters and are + therefore truncated. Note that during database schema generation + (\cb{--generate-schema}) ODB detects when such truncations lead + to name conflicts and issues diagnostics even without this option + specified." + }; + // // SQL Server-specific options. // diff --git a/odb/relational/model.cxx b/odb/relational/model.cxx index 6a65803..d7c2bd8 100644 --- a/odb/relational/model.cxx +++ b/odb/relational/model.cxx @@ -107,14 +107,11 @@ namespace relational << endl; if (e.dup.kind () == "index") - error (d) << "use #pragma db index to change one of the names" - << endl; + info (d) << "use #pragma db index to change its name" << endl; else if (e.dup.kind () == "table") - error (d) << "use #pragma db table to change one of the names" - << endl; + info (d) << "use #pragma db table to change its name" << endl; else - error (d) << "use #pragma db column to change its name" - << endl; + info (d) << "use #pragma db column to change its name" << endl; throw operation_failed (); } diff --git a/odb/relational/model.hxx b/odb/relational/model.hxx index bb83e02..9b53104 100644 --- a/odb/relational/model.hxx +++ b/odb/relational/model.hxx @@ -618,7 +618,7 @@ namespace relational { in = &model_.new_node ( id + ".id", sin->type, sin->method, sin->options); - in->set ("cxx-location", sin->loc); + in->set ("cxx-location", location (sin->loc)); } else { @@ -675,7 +675,7 @@ namespace relational { in = &model_.new_node ( id + ".index", sin->type, sin->method, sin->options); - in->set ("cxx-location", sin->loc); + in->set ("cxx-location", location (sin->loc)); } else { diff --git a/odb/relational/oracle/schema.cxx b/odb/relational/oracle/schema.cxx index 829a37a..a775529 100644 --- a/odb/relational/oracle/schema.cxx +++ b/odb/relational/oracle/schema.cxx @@ -2,6 +2,11 @@ // copyright : Copyright (c) 2009-2013 Code Synthesis Tools CC // license : GNU GPL v3; see accompanying LICENSE file +#include +#include // pair + +#include + #include #include @@ -228,12 +233,121 @@ namespace relational // // Create. // + static sema_rel::uname + truncate (location const& l, const char* kind, sema_rel::uname n, bool w) + { + if (n.size () > 30) + { + if (w) + warn (l) << kind << " name '" << n << "' is longer than 30 " + << "characters and will be truncated" << endl; + + n.resize (30); + } + + return n; + } + + static sema_rel::qname + truncate (location const& l, + const char* kind, + sema_rel::qname const& n, + bool w) + { + // Don't bother verifying the schema name since that is + // specified explicitly and in a single place. + // + qname r (n.qualifier ()); + r.append (truncate (l, kind, n.uname (), w)); + return r; + } + + template + struct scope + { + typedef std::map > map; + + scope (const char* k, const char* p, bool w) + : kind_ (k), prag_ (p), warn_ (w) {} + + void + check (location const& l, N const& n) + { + N tn (truncate (l, kind_, n, warn_)); + + pair r ( + map_.insert (make_pair (tn, make_pair (n, l)))); + + if (r.second) + return; + + error (l) << kind_ << " name '" << tn << "' conflicts with an " + << "already defined " << kind_ << " name" << endl; + + if (tn != n) + info (l) << kind_ << " name '" << tn << "' is truncated '" + << n << "'" << endl; + + N const& n1 (r.first->second.first); + location const& l1 (r.first->second.second); + + info (l1) << "conflicting " << kind_ << " is defined here" << endl; + + if (tn != n) + info (l1) << "conflicting " << kind_ << " name '" << tn + << "' is truncated '" << n1 << "'" << endl; + + info (l) << "use #pragma db " << prag_ << " to change one of " + << "the names" << endl; + + throw operation_failed (); + } + + void + clear () {map_.clear ();} + + const char* kind_; + const char* prag_; + bool warn_; + map map_; + }; + + struct scopes + { + scopes (bool warn) + : tables ("table", "table", warn), + fkeys ("foreign key", "column", warn), // Change column name. + indexes ("index", "index", warn), + sequences ("sequence", "table", warn), // Change table name. + columns ("column", "column", warn) {} + + // In Oracle, all these entities are in their own name spaces, + // as in an index and a foreign key with the same name do not + // conflict. + // + scope tables; + scope fkeys; // Global but can't have schema. + scope indexes; + scope sequences; + scope columns; + }; struct create_column: relational::create_column, context { create_column (base const& x): base (x) {} virtual void + traverse (sema_rel::column& c) + { + // Check name trunction and conflicts. + // + if (scopes* s = static_cast (context::extra)) + s->columns.check (c.get ("cxx-location"), c.name ()); + + base::traverse (c); + } + + virtual void traverse (sema_rel::add_column& ac) { if (first_) @@ -272,8 +386,24 @@ namespace relational create_foreign_key (base const& x): base (x) {} virtual void + traverse_create (sema_rel::foreign_key& fk) + { + // Check name trunction and conflicts. + // + if (scopes* s = static_cast (context::extra)) + s->fkeys.check (fk.get ("cxx-location"), fk.name ()); + + base::traverse_create (fk); + } + + virtual void traverse_add (sema_rel::foreign_key& fk) { + // Check name trunction and conflicts. + // + if (scopes* s = static_cast (context::extra)) + s->fkeys.check (fk.get ("cxx-location"), fk.name ()); + os << endl << " ADD CONSTRAINT "; create (fk); @@ -293,6 +423,12 @@ namespace relational sema_rel::table& t (static_cast (in.scope ())); sema_rel::qname n (t.name ().qualifier ()); n.append (in.name ()); + + // Check name trunction and conflicts. + // + if (scopes* s = static_cast (context::extra)) + s->indexes.check (in.get ("cxx-location"), n); + return quote_id (n); } }; @@ -305,6 +441,17 @@ namespace relational void traverse (sema_rel::table& t) { + // Check name trunction and conflicts. + // + if (scopes* s = static_cast (context::extra)) + { + if (pass_ == 1) + { + s->tables.check (t.get ("cxx-location"), t.name ()); + s->columns.clear (); + } + } + base::traverse (t); if (pass_ == 1) @@ -320,11 +467,16 @@ namespace relational if (pk != 0 && pk->auto_ ()) { - string qs ( - quote_id (qname::from_string (pk->extra ()["sequence"]))); + // Already qualified with the table's schema, if any. + // + sema_rel::qname n ( + qname::from_string (pk->extra ()["sequence"])); + + if (scopes* s = static_cast (context::extra)) + s->sequences.check (pk->get ("cxx-location"), n); pre_statement (); - os_ << "CREATE SEQUENCE " << qs << endl + os_ << "CREATE SEQUENCE " << quote_id (n) << endl << " START WITH 1 INCREMENT BY 1" << endl; post_statement (); } @@ -333,6 +485,21 @@ namespace relational }; entry create_table_; + struct create_model: relational::create_model, context + { + create_model (base const& x): base (x) {} + + void + traverse (sema_rel::model& m) + { + scopes s (options.oracle_warn_truncation ()); + context::extra = &s; + base::traverse (m); + context::extra = 0; + } + }; + entry create_model_; + // // Alter. // -- cgit v1.1